O pacote dplyr
O dplyr é o pacote mais útil para realizar transformação de dados, aliando simplicidade e eficiência de uma forma elegante. Os scripts em R que fazem uso inteligente dos verbos dplyr e as facilidades do operador pipe tendem a ficar mais legíveis e organizados sem perder velocidade de execução.
As principais funções do dplyr são:
select() - seleciona colunas
arrange() - ordena a base
filter() - filtra linhas
mutate() - cria/modifica colunas
group_by() - agrupa a base
summarise() - sumariza a base
Todas essas funções seguem as mesmas características:
- O input é sempre uma
tibble e o output é sempre um tibble.
- Colocamos a
tibble no primeiro argumento e o que queremos fazer nos outros argumentos.
- A utilização é facilitada com o emprego do operador
%>%.
As principais vantagens de se usar o dplyr em detrimento das funções do R base são:
- Manipular dados se torna uma tarefa muito mais simples.
- O código fica mais intuitivo de ser escrito e mais simples de ser lido.
- O pacote
dplyr utiliza C e C++ por trás da maioria das funções, o que geralmente torna o código mais rápido.
- É possível trabalhar com diferentes fontes de dados, como bases relacionais (SQL) e
data.table.
Se você ainda não tiver o dplyr instalado, rode o código abaixo.
# install.packages("dplyr")
library(dplyr)
Neste capítulo, vamos trabalhar com uma base de filmes do IMDB.
Assim, utilizaremos o objeto imdb para acessar os dados.
Agora, vamos avaliar com mais detalhes as principais funções do pacote dplyr.
Selecionando colunas
Para selecionar colunas, utilizamos a função select().
O primeiro argumento da função é a base de dados e os demais argumentos são os nomes das colunas que você gostaria de selecionar. Repare que você não precisa colocar o nome da coluna entre aspas.
select(imdb, titulo)
Você também pode selecionar várias colunas.
select(imdb, titulo, ano, orcamento)
O operador : é muito útil para selecionar colunas consecutivas.
select(imdb, titulo:cor)
O dplyr possui o conjunto de funções auxiliares muito úteis para seleção de colunas. As principais são:
starts_with(): para colunas que começam com um texto padrão
ends_with(): para colunas que terminam com um texto padrão
contains(): para colunas que contêm um texto padrão
Selecionamos a seguir todas as colunas que começam com o texto “ator”.
select(imdb, starts_with("ator"))
Para retirar colunas da base, base acrescentar um - antes da seleção.
imdb %>%
select(-ano, - diretor)
imdb %>%
select(-starts_with("ator"))
Exercícios
Utilize a base imdb nos exercícios a seguir.
1. Teste aplicar a função glimpse() do pacote {dplyr} à base imdb. O que ela faz?
2. Crie uma tabela com apenas as colunas titulo, diretor, e orcamento. Salve em um objeto chamado imdb_simples.
3. Selecione apenas as colunas ator_1, ator_2 e ator_3 usando o ajudante contains().
4. Usando a função select() (e seus ajudantes), escreva códigos que retornem a base IMDB sem as colunas ator_1, ator_2 e ator_3. Escreva todas as soluções diferentes que você conseguir pensar.
Ordenando a base
Para ordenar linhas, utilizamos a função arrange(). O primeiro argumento é a base de dados. Os demais argumentos são as colunas pelas quais queremos ordenar as linhas. No exemplo a seguir, ordenamos as linhas da base por ordem crescente de orçamento.
arrange(imdb, orcamento)
Também podemos ordenar de forma decrescente usando a função desc().
arrange(imdb, desc(orcamento))
E claro, ordenar segundo duas ou mais colunas.
arrange(imdb, desc(ano), desc(orcamento))
Exercícios
Utilize a base imdb nos exercícios a seguir.
1. Ordene os filmes em ordem crescente de ano e decrescente de receita e salve em um objeto chamado filmes_ordenados.
2. Selecione apenas as colunas titulo e orcamento e então ordene de forma decrescente pelo orcamento.
O pipe em ação
Na grande maioria dos casos, vamos aplicar mais de uma função de manipulação em uma base para obtermos a tabela que desejamos. Poderíamos, por exemplo, querer uma tabela apenas com o título e ano dos filmes, ordenada de forma crescente de lançamento. Para fazer isso, poderíamos aninhar as funções
arrange(select(imdb, titulo, ano), ano)
ou criar um objeto intermediário
tab_titulo_ano <- select(imdb, titulo, ano)
arrange(tab_titulo_ano, ano)
Os dois códigos funcionam e levam ao mesmo resultado, mas não são muito boas.
A primeira alternativa é ruim de escrever, já que precisamos escrever primeiro a função que roda por último, e de ler, pois é difícil identificar qual argumento pertence a qual função.
A segunda alternativa é ruim pois exige a criação de objetos auxiliares. Se quiséssimos aplicar 10 operações na base, precisaríamos criar 9 objetos intermediários.
A solução para aplicar diversas operações de manipulação em uma base de dados é aplicar o operador pipe: %>%.
imdb %>%
select(titulo, ano) %>%
arrange(ano)
O que está sendo feito no código com pipe? Da primeira para a segunda linha, estamos aplicando a função select() à base imdb. Da segunda para a terceira, estamos aplicando a função arrange() à base resultante da função select().
O resultado desse código é identico às tentativas sem pipe, com a vantagem de termos escrito o código na ordem em que as funções são aplicadas, de termos um código muito mais legível e de não precisarmos utilizar objetos intermediários.
Filtrando linhas
Para filtrar valores de uma coluna da base, utilizamos a função filter().
imdb %>% filter(nota_imdb > 9)
Podemos selecionar apenas as colunas título e nota para visualizarmos as notas:
imdb %>%
filter(nota_imdb > 9) %>%
select(titulo, nota_imdb)
Podemos estender o filtro para duas ou mais colunas. Para isso, separamos cada operação por uma vírgula.
imdb %>% filter(ano > 2010, nota_imdb > 8.5)
Também podemos fazer operações com as colunas da base dentro da função filter. O código abaixo devolve uma tabela apenas com os filmes que lucraram.
imdb %>% filter(receita - orcamento > 0)
Naturalmente, podemos filtrar colunas categóricas. O exemplo abaixo retorna uma tabela apenas com os filmes com a Angelina Jolie Pitt ou o Brad Pitt no papel principal.
imdb %>%
filter(ator_1 %in% c('Angelina Jolie Pitt', "Brad Pitt"))
Para filtrar textos sem correspondência exata, podemos utilizar a função auxiliar str_detect() do pacote {stringr}. Ela serve para verificar se cada string de um vetor contém um determinado padrão de texto.
library(stringr)
str_detect(
string = c("a", "aa","abc", "bc", "A", NA),
pattern = "a"
)
## [1] TRUE TRUE TRUE FALSE FALSE NA
Podemos utilizá-la para filtrar apenas os filmes que contêm o gênero ação.
# A coluna gêneros apresenta todos os gêneros dos filmes concatenados
imdb$generos[1:6]
## [1] "Action|Adventure|Fantasy|Sci-Fi"
## [2] "Action|Adventure|Fantasy"
## [3] "Action|Thriller"
## [4] "Action|Adventure|Sci-Fi"
## [5] "Action|Adventure|Romance"
## [6] "Adventure|Animation|Comedy|Family|Fantasy|Musical|Romance"
# Podemos detectar se o gênero Action aparece na string
str_detect(
string = imdb$generos[1:6],
pattern = "Action"
)
## [1] TRUE TRUE TRUE TRUE TRUE FALSE
# Aplicamos essa lógica dentro da função filter, para a coluna completa
imdb %>% filter(str_detect(generos, "Action"))
Exercícios
Utilize a base imdb nos exercícios a seguir.
1. Crie um objeto chamado filmes_pb apenas com filmes preto e branco.
2. Crie um objeto chamado curtos_legais com filmes de 90 minutos ou menos de duração e nota no imdb maior do que 8.5.
3. Retorne tabelas (tibbles) apenas com:
a. filmes coloridos anteriores a 1950;
b. filmes do “Woody Allen” ou do “Wes Anderson”;
c. filmes do “Steven Spielberg” ordenados de forma decrescente por ano, mostrando apenas as colunas titulo e ano;
d. filmes que tenham “Action” ou “Comedy” entre os seus gêneros;
e. filmes que tenham “Action” e “Comedy” entre os seus gêneros e tenha nota_imdb maior que 8;
f. filmes que não possuem informação tanto de receita quanto de orçamento (isto é, possuem NA em ambas as colunas).
Modificando e criando novas colunas
Para modificar uma coluna existente ou criar uma nova coluna, utilizamos a função mutate(). O código abaixo divide os valores da coluna duração por 60, mudando a unidade de medida dessa variável de minutos para horas.
imdb %>% mutate(duracao = duracao/60)
Também poderíamos ter criado essa variável em uma nova coluna. Repare que a nova coluna duracao_horas é colocada no final da tabela.
imdb %>% mutate(duracao_horas = duracao/60)
Podemos fazer qualquer operação com uma ou mais colunas. A única regra é que o resultado da operação retorne um vetor com comprimento igual ao número de linhas da base (ou com comprimento 1 para distribuir um mesmo valor em todas as linhas). Você também pode criar/modificar quantas colunas quiser dentro de um mesmo mutate.
imdb %>%
mutate(lucro = receita - orcamento, pais = "Estados Unidos") %>%
select(titulo, lucro, pais)
Exercícios
Utilize a base imdb nos exercícios a seguir.
1. Crie uma coluna chamada prejuizo (orcamento - receita) e salve a nova tabela em um objeto chamado imdb_prejuizo. Em seguida, filtre apenas os filmes que deram prejuízo e ordene a tabela por ordem crescente de prejuízo.
2. Fazendo apenas uma chamada da função mutate(), crie as seguintes colunas novas na base imdb:
a. lucro = receita - orcamento
b. lucro_medio
c. lucro_relativo = (lucro - lucro_medio)/lucro_medio
d. houve_lucro = ifelse(lucro > 0, "sim", "não")
3. Crie uma nova coluna que classifique o filme em "recente" (posterior a 2000) e "antigo" (de 2000 para trás).
Summarisando a base
Sumarização é a técnica de se resumir um conjunto de dados utilizando alguma métrica de interesse. A média, a mediana, a variância, a frequência, a proporção, por exemplo, são tipos de sumarização que trazem diferentes informações sobre uma variável.
Para sumarizar uma coluna da base, utilizamos a função summarize(). O código abaixo resume a coluna orçamento pela sua média.
imdb %>% summarize(media_orcamento = mean(orcamento, na.rm = TRUE))
Repare que a saída da função continua sendo uma tibble.
Podemos calcular diversas sumarizações diferentes em um mesmo summarize. Cada sumarização será uma coluna da nova base.
imdb %>% summarise(
media_orcamento = mean(orcamento, na.rm = TRUE),
mediana_orcamento = median(orcamento, na.rm = TRUE),
variancia_orcamento = var(orcamento, na.rm = TRUE)
)
E também sumarizar diversas colunas.
imdb %>% summarize(
media_orcamento = mean(orcamento, na.rm = TRUE),
media_receita = mean(receita, na.rm = TRUE),
media_lucro = mean(receita - orcamento, na.rm = TRUE)
)
Muitas vezes queremos sumarizar uma coluna agrupada pelas categorias de uma segunda coluna. Para isso, além do summarize, utilizamos também a função group_by().
O código a seguir calcula a receita média dos filmes para cada categoria da coluna “cor”.
imdb %>%
group_by(cor) %>%
summarise(receita_media = mean(receita, na.rm = TRUE))
A única alteração que a função group_by() faz na base é a marcação de que a base está agrupada.
imdb %>% group_by(cor)
Exercícios
Utilize a base imdb nos exercícios a seguir.
1. Calcule a duração média e mediana dos filmes da base.
2. Calcule o lucro médio dos filmes com duração menor que 60 minutos.
3. Apresente na mesma tabela o lucro médio dos filmes com duracao menor que 60 minutos e o lucro médio dos filmes com duracao maior ou igual a 60 minutos.
4. Retorne tabelas (tibbles) apenas com:
a. a nota IMDB média dos filmes por tipo de classificacao;
b. a receita média e mediana dos filmes por ano;
c. apenas o nome dos diretores com mais de 10 filmes.
Juntando duas bases
Podemos juntar duas tabelas a partir de uma (coluna) chave utilizando a função left_join(). Como exempo, vamos inicialmente calcular o lucro médio dos filmes de cada diretor e salvar no objeto tab_lucro_diretor.
tab_lucro_diretor <- imdb %>%
group_by(diretor) %>%
summarise(lucro_medio = mean(receita - orcamento, na.rm = TRUE))
tab_lucro_diretor
E se quisermos colocar essa informação na base original? Basta usar a função left_join() utilizando a coluna diretor como chave. Observe que a coluna lucro_medio aparece agora no fim da tabela.
imdb_com_lucro_medio <- left_join(imdb, tab_lucro_diretor, by = "diretor")
imdb_com_lucro_medio
Na tabela imdb_com_lucro_medio, como na tabela imdb, cada linha continua a representar um filme diferente, mas agora temos também a informação do lucro médio do diretor de cada filme.
A primeira linha, por exemplo, traz as informações do filme Avatar. O valor do lucro_medio nessa linha representa o lucro médio de todos os filmes do James Cameron, que é o diretor de Avatar. Com essa informação, podemos calcular o quanto o lucro do Avatar se afasta do lucro médio do James Cameron.
imdb_com_lucro_medio %>%
mutate(
lucro = receita - orcamento,
lucro_relativo = (lucro - lucro_medio)/lucro_medio,
lucro_relativo = scales::percent(lucro_relativo)
) %>%
select(titulo, diretor, lucro, lucro_medio, lucro_relativo)
Observamos então que o Avatar obteve um lucro aproximadamente 169% maior que a média dos filmes do James Cameron.
Além da função left_join(), também são muito utilizadas as funções right_join() e full_join().
right_join(): retorna todas as linhas da base y e todas as colunas das bases x e y. Linhas de y sem correspondentes em x receberão NA na nova base.
full_join(): retorna todas as linhas e colunas de xe y. Valores sem correspondência entre as bases receberão NA na nova base.
A figura a seguir esquematiza as operações dessas funções:
Exercícios
1. Utilize a base imdb para resolver os itens a seguir.
a. Salve em um novo objeto uma tabela com a nota média dos filmes de cada diretor. Essa tabela deve conter duas colunas (diretor e nota_imdb_media) e cada linha deve ser um diretor diferente.
b. Use o left_join() para trazer a coluna nota_imdb_media da tabela do item anterior para a tabela imdb original.
dplyr 1.0
A versão 1.0 do pacote dplyr foi oficialmente lançada em junho de 2020 e contou com diversas novidades. Vamos falar das principais mudanças:
A nova função across(), que facilita aplicar uma mesma operação em várias colunas.
A repaginada função rowwise(), para fazer operações por linha.
Novas funcionalidades das funções select() e rename() e a nova função relocate().
Para trabalhar essas funções, vamos utilizar a base casas do pacote dados. Para instalar esse pacote, rode os códigos abaixo:
# install.packages("remotes")
# remotes::install_github("cienciadedatos/dados")
Para trazer os dados para o nosso environment, podemos rodar:
casas <- dados::casas
A base casas possui dados de venda de casas na cidade de Ames, nos Estados Unidos. São 2930 linhas e 77 colunas, sendo que cada linha corresponde a uma casa vendida e cada coluna a uma característica da casa ou da venda. Essa versão é uma tradução da base original, que pode ser encontrada no pacote AmesHousing:
install.packages("AmesHousing")
data(ames_raw, package = "AmesHousing")
A função across()
A função across() substitui a família de verbos _all(), _if e _at(). A ideia é facilitar a aplicação de uma operação a diversas colunas da base. Para sumarizar a base para mais de uma variável, antigamente poderíamos fazer:
casas %>%
group_by(geral_qualidade) %>%
summarise(
lote_area_media = mean(lote_area, na.rm = TRUE),
venda_valor_medio = mean(venda_valor, na.rm = TRUE)
)
Ou então utilizar a função summarise_at():
casas %>%
group_by(geral_qualidade) %>%
summarise_at(
.vars = vars(lote_area, venda_valor),
list(mean),
na.rm = TRUE
)
Com a nova função across(), fazemos:
casas %>%
group_by(geral_qualidade) %>%
summarise(across(
.cols = c(lote_area, venda_valor),
.fns = mean,
na.rm = TRUE
))
A sintaxe é parecida com a função summarise_at(), mas agora não precisamos mais usar a função vars() e nem usar list(nome_da_funcao)para definir a função aplicada nas colunas.
Usando across(), podemos facilmente aplicar uma função em todas as colunas da nossa base. Abaixo, calculamos o número de valores distintos para todas as variáveis da base casas. O padrão do parâmetro .cols é everithing(), que representa “todas as colunas”.
casas %>%
summarise(across(.fns = n_distinct))
Para fazer essa mesma operação, antes utilizaríamos a função summarise_all().
casas %>%
summarise_all(.funs = ~n_distinct(.x))
Se quisermos selecionar as colunas a serem modificadas a partir de um teste lógico, utilizamos o ajudante where().
No exemplo abaixo, calculamos o número de valores distintos das colunas de categóricas.
casas %>%
summarise(across(where(is.character), n_distinct))
Todas as colunas da base resultante eram colunas com classe character na base casas.
Anteriormente, utilizávamos a função summarise_if().
casas %>%
summarise_if(is.character, n_distinct)
Com o across(), podemos combinar os efeitos de um summarise_if() e um summarise_at() em um único summarise(). A seguir, calculamos as áreas médias, garantindo que pegamos apenas variáveis numéricas.
casas %>%
summarise(across(where(is.numeric) & contains("_area"), mean, na.rm = TRUE))
Além disso, com a função across(), podemos fazer sumarizações complexas que não seria possível utilizando apenas as funções summarise(), summarise_if() e summarise_at(). No exemplo a seguir, calculamos a média das áreas, o número de NAs de variáveis categóricas e o número de casas para cada tipo de fundação. Tudo em um mesmo summarise()!
casas %>%
group_by(fundacao_tipo) %>%
summarise(
across(where(is.numeric) & contains("area"), mean, na.rm = TRUE),
across(where(is.character), ~sum(is.na(.x))),
n_obs = n(),
) %>%
select(1:4, n_obs)
Embora a nova sintaxe, usando across(), não seja muito diferente do que fazíamos antes, realizar sumarizações complexas não é a única vantagem desse novo framework.
O across() pode ser utilizado em todos os verbos do {dplyr} (com exceção do select() e rename(), já que ele não trás vantagens com relação ao que já podia ser feito) e isso unifica o modo de fazermos essas operações no dplyr. Em vez de termos uma família de funções para cada verbo, temos agora apenas o próprio verbo e a função across().
Vamos ver um exemplo para o mutate() e para o filter().
O código abaixo transforma todas as variáveis que possuem “area” no nome, passando os valores de pés quadrados para metros quadrados.
casas %>%
mutate(across(
contains("area"),
~ .x / 10.764
))
Já o código a seguir filtra apenas as casas que possuem varanda aberta, cerca e lareira (o NA nessa base significa que a casa não possui tal característica).
casas %>%
filter(across(
c(varanda_aberta_area, cerca_qualidade, lareira_qualidade),
~!is.na(.x)
))
Não precisamos do across() na hora de selecionar colunas. A função select() já usa naturalmente o mecanismo de seleção de colunas que o across() proporciona.
casas %>%
select(where(is.numeric))
O mesmo vale para o rename(). Se quisermos renomer várias colunas, a partir de uma função, utilizamos o rename_with().
casas %>%
rename_with(toupper, contains("venda"))
A função relocate()
O {dplyr} possui agora uma função própria para reorganizar colunas: relocate(). Por padrão, ela coloca uma ou mais colunas no começo da base.
casas %>%
relocate(venda_valor, venda_tipo)
Podemos usar os argumentos .after e .before para fazer mudanças mais complexas.
O código baixo coloca a coluna venda_ano depois da coluna construcao_ano.
casas %>%
relocate(venda_ano, .after = construcao_ano)
O código baixo coloca a coluna venda_ano antes da coluna construcao_ano.
casas %>%
relocate(venda_ano, .before = construcao_ano)
A função rowwise()
Por fim, vamos discutir operações feitas por linha. Tome como exemplo a tabela abaixo. Ela apresenta as notas de alunos em quatro provas.
tab_notas <- tibble(
student_id = 1:5,
prova1 = sample(0:10, 5),
prova2 = sample(0:10, 5),
prova3 = sample(0:10, 5),
prova4 = sample(0:10, 5)
)
tab_notas
Se quisermos gerar uma coluna com a nota média de cada aluno nas quatro provas, não poderíamos usar o mutate() diretamente.
tab_notas %>% mutate(media = mean(c(prova1, prova2, prova3, prova4)))
Neste caso, todas as colunas estão sendo empilhadas e gerando uma única média, passada a todas as linhas da coluna media.
Para fazermos a conta para cada aluno, podemos agrupar por aluno. Agora sim a média é calculada apenas nas notas de cada estudante.
tab_notas %>%
group_by(student_id) %>%
mutate(media = mean(c(prova1, prova2, prova3, prova4)))
Também podemos nos aproveitar da sintaxe do across() neste caso. Para isso, precisamos substutir a função c() pela função c_across().
tab_notas %>%
group_by(student_id) %>%
mutate(media = mean(c_across(starts_with("prova"))))
Equivalentemente ao group_by(), neste caso, podemos usar a função rowwise().
tab_notas %>%
rowwise(student_id) %>%
mutate(media = mean(c_across(starts_with("prova"))))
Ela é muito útil quando queremos fazer operação por linhas, mas não temos uma coluna de identificação. Por padrão, se não indicarmos nenhuma coluna, cada linha será um “grupo”.
tab_notas %>%
rowwise() %>%
mutate(media = mean(c_across(starts_with("prova"))))
Veja que student_id não é passada para a função rowwise(). Não precisaríamos dessa coluna na base para reproduzir a geração da columa media neste caso.
Exercícios
A base casas abaixo pode ser encontrada a partir do código abaixo:
remotes::install_github("cienciadedatos/dados")
library(dados)
dados::casas
1. Reescreva os códigos abaixo utilizando as funções across() e where().
a.
casas %>%
group_by(geral_qualidade) %>%
summarise(
acima_solo_area_media = mean(acima_solo_area, na.rm = TRUE),
garagem_area_media = mean(garagem_area, na.rm = TRUE),
valor_venda_medio = mean(venda_valor, na.rm = TRUE)
)
b.
casas %>%
filter_at(
vars(porao_qualidade, varanda_fechada_area, cerca_qualidade),
~!is.na(.x)
)
c.
casas %>%
mutate_if(is.character, ~tidyr::replace_na(.x, replace = "Não possui"))
2. Utilizando a base casas, resolva os itens a seguir.
- b. Utilize o código feito na letra (a) para agrupar a base
casas pela variável venda_valor categorizada e calcular todas as áreas médias para cada uma dessas categorias.
3. Escreva um código que receba a base casas e retorne uma tabela com apenas
a. as colunas referentes à garagem da casa.
b. as colunas referentes a variáveis de qualidade.
c. colunas numéricas que representam áreas da casa e do terreno.
d. colunas numéricas.
e. colunas referentes à piscina, porão e o valor de venda.
4. Usando a função rename_with(), troque todos os "_" dos nomes das colunas por um espaço " ".
5. Escreva um código para colocar todas as colunas relativas a venda no começo da base casas.
6. 5. Escreva um código para colocar todas as colunas numéricas da base casas no começo da tabela e todas as colunas categóricas no final.
LS0tDQp0aXRsZTogIkFwcmVuZGVuZG8gYSB1c2FyIG8gRHBseXIiDQpydW5uaW5naGVhZGVyOiAiIiAjIG9ubHkgZm9yIHBkZiBvdXRwdXQNCnN1YnRpdGxlOiAiTcOpdG9kb3MgQ29tcHV0YWNpb25haXMgZW0gUiIgIyBvbmx5IGZvciBodG1sIG91dHB1dA0KYXV0aG9yOiAiUHJvZi4gQ2xhdWRpYW5vIE5ldG8iDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQplbmNvZGluZzogImlzby04ODU5LTEiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQoNCiMgTyBwYWNvdGUgZHBseXIgeyNkcGx5cn0NCg0KTyBgZHBseXJgIMOpIG8gcGFjb3RlIG1haXMgw7p0aWwgcGFyYSByZWFsaXphciB0cmFuc2Zvcm1hw6fDo28gZGUgZGFkb3MsIGFsaWFuZG8gc2ltcGxpY2lkYWRlIGUgZWZpY2nDqm5jaWEgZGUgdW1hIGZvcm1hIGVsZWdhbnRlLiBPcyBzY3JpcHRzIGVtIFIgcXVlIGZhemVtIHVzbyBpbnRlbGlnZW50ZSBkb3MgdmVyYm9zIGBkcGx5cmAgZSBhcyBmYWNpbGlkYWRlcyBkbyBvcGVyYWRvciBfcGlwZV8gdGVuZGVtIGEgZmljYXIgbWFpcyBsZWfDrXZlaXMgZSBvcmdhbml6YWRvcyBzZW0gcGVyZGVyIHZlbG9jaWRhZGUgZGUgZXhlY3XDp8Ojby4NCg0KQXMgcHJpbmNpcGFpcyBmdW7Dp8O1ZXMgZG8gYGRwbHlyYCBzw6NvOg0KDQotIGBzZWxlY3QoKWAgLSBzZWxlY2lvbmEgY29sdW5hcw0KLSBgYXJyYW5nZSgpYCAtIG9yZGVuYSBhIGJhc2UNCi0gYGZpbHRlcigpYCAtIGZpbHRyYSBsaW5oYXMNCi0gYG11dGF0ZSgpYCAtIGNyaWEvbW9kaWZpY2EgY29sdW5hcw0KLSBgZ3JvdXBfYnkoKWAgLSBhZ3J1cGEgYSBiYXNlDQotIGBzdW1tYXJpc2UoKWAgLSBzdW1hcml6YSBhIGJhc2UNCg0KVG9kYXMgZXNzYXMgZnVuw6fDtWVzIHNlZ3VlbSBhcyBtZXNtYXMgY2FyYWN0ZXLDrXN0aWNhczoNCg0KLSBPIF9pbnB1dF8gIMOpIHNlbXByZSB1bWEgYHRpYmJsZWAgZSBvIF9vdXRwdXRfICDDqSBzZW1wcmUgdW0gYHRpYmJsZWAuDQotIENvbG9jYW1vcyBhIGB0aWJibGVgIG5vIHByaW1laXJvIGFyZ3VtZW50byBlIG8gcXVlIHF1ZXJlbW9zIGZhemVyIG5vcyBvdXRyb3MgYXJndW1lbnRvcy4NCi0gQSB1dGlsaXphw6fDo28gw6kgZmFjaWxpdGFkYSBjb20gbyBlbXByZWdvIGRvIG9wZXJhZG9yIGAlPiVgLg0KDQpBcyBwcmluY2lwYWlzIHZhbnRhZ2VucyBkZSBzZSB1c2FyIG8gYGRwbHlyYCBlbSBkZXRyaW1lbnRvIGRhcyBmdW7Dp8O1ZXMgZG8gUiBiYXNlIHPDo286DQoNCi0gTWFuaXB1bGFyIGRhZG9zIHNlIHRvcm5hIHVtYSB0YXJlZmEgbXVpdG8gbWFpcyBzaW1wbGVzLg0KLSBPIGPDs2RpZ28gZmljYSBtYWlzIGludHVpdGl2byBkZSBzZXIgZXNjcml0byBlIG1haXMgc2ltcGxlcyBkZSBzZXIgbGlkby4NCi0gTyBwYWNvdGUgYGRwbHlyYCB1dGlsaXphIGBDYCBlIGBDKytgIHBvciB0csOhcyBkYSBtYWlvcmlhIGRhcyBmdW7Dp8O1ZXMsIG8gcXVlIGdlcmFsbWVudGUgdG9ybmEgbyBjw7NkaWdvIG1haXMgcsOhcGlkby4NCi0gw4kgcG9zc8OtdmVsIHRyYWJhbGhhciBjb20gZGlmZXJlbnRlcyBmb250ZXMgZGUgZGFkb3MsIGNvbW8gYmFzZXMgcmVsYWNpb25haXMgKFNRTCkgZSBgZGF0YS50YWJsZWAuDQoNClNlIHZvY8OqIGFpbmRhIG7Do28gdGl2ZXIgbyBgZHBseXJgIGluc3RhbGFkbywgcm9kZSBvIGPDs2RpZ28gYWJhaXhvLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpDQpsaWJyYXJ5KGRwbHlyKQ0KYGBgDQoNCk5lc3RlIGNhcMOtdHVsbywgdmFtb3MgdHJhYmFsaGFyIGNvbSB1bWEgYmFzZSBkZSBmaWxtZXMgZG8gSU1EQi4NCg0KQXNzaW0sIHV0aWxpemFyZW1vcyBvIG9iamV0byBgaW1kYmAgcGFyYSBhY2Vzc2FyIG9zIGRhZG9zLg0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkoZHBseXIpDQppbWRiIDwtIHJlYWRyOjpyZWFkX3JkcygiaHR0cHM6Ly9naXRodWIuY29tL2N1cnNvLXIvbGl2cm8tbWF0ZXJpYWwvcmF3L21hc3Rlci9hc3NldHMvZGF0YS9pbWRiLnJkcyIpDQpgYGANCg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCmltZGINCmBgYA0KDQoNCkFnb3JhLCB2YW1vcyBhdmFsaWFyIGNvbSBtYWlzIGRldGFsaGVzIGFzIHByaW5jaXBhaXMgZnVuw6fDtWVzIGRvIHBhY290ZSBgZHBseXJgLg0KDQojIyMgU2VsZWNpb25hbmRvIGNvbHVuYXMNCg0KUGFyYSBzZWxlY2lvbmFyIGNvbHVuYXMsIHV0aWxpemFtb3MgYSBmdW7Dp8OjbyBgc2VsZWN0KClgLg0KDQpPIHByaW1laXJvIGFyZ3VtZW50byBkYSBmdW7Dp8OjbyDDqSBhIGJhc2UgZGUgZGFkb3MgZSBvcyBkZW1haXMgYXJndW1lbnRvcyBzw6NvIG9zIG5vbWVzIGRhcyBjb2x1bmFzIHF1ZSB2b2PDqiBnb3N0YXJpYSBkZSBzZWxlY2lvbmFyLiBSZXBhcmUgcXVlIHZvY8OqIG7Do28gcHJlY2lzYSBjb2xvY2FyIG8gbm9tZSBkYSBjb2x1bmEgZW50cmUgYXNwYXMuDQoNCmBgYHtyfQ0Kc2VsZWN0KGltZGIsIHRpdHVsbykNCmBgYA0KDQoNClZvY8OqIHRhbWLDqW0gcG9kZSBzZWxlY2lvbmFyIHbDoXJpYXMgY29sdW5hcy4NCg0KYGBge3J9DQpzZWxlY3QoaW1kYiwgdGl0dWxvLCBhbm8sIG9yY2FtZW50bykNCmBgYA0KDQpPIG9wZXJhZG9yIGA6YCDDqSBtdWl0byDDunRpbCBwYXJhIHNlbGVjaW9uYXIgY29sdW5hcyBjb25zZWN1dGl2YXMuDQoNCmBgYHtyfQ0Kc2VsZWN0KGltZGIsIHRpdHVsbzpjb3IpDQpgYGANCg0KDQpPIGBkcGx5cmAgcG9zc3VpIG8gY29uanVudG8gZGUgZnVuw6fDtWVzIGF1eGlsaWFyZXMgbXVpdG8gw7p0ZWlzIHBhcmEgc2VsZcOnw6NvIGRlIGNvbHVuYXMuIEFzIHByaW5jaXBhaXMgc8OjbzoNCg0KLSBgc3RhcnRzX3dpdGgoKWA6IHBhcmEgY29sdW5hcyBxdWUgY29tZcOnYW0gY29tIHVtIHRleHRvIHBhZHLDo28NCi0gYGVuZHNfd2l0aCgpYDogcGFyYSBjb2x1bmFzIHF1ZSB0ZXJtaW5hbSBjb20gdW0gdGV4dG8gcGFkcsOjbw0KLSBgY29udGFpbnMoKWA6IHBhcmEgY29sdW5hcyBxdWUgY29udMOqbSB1bSB0ZXh0byBwYWRyw6NvDQoNClNlbGVjaW9uYW1vcyBhIHNlZ3VpciB0b2RhcyBhcyBjb2x1bmFzIHF1ZSBjb21lw6dhbSBjb20gbyB0ZXh0byAiYXRvciIuDQoNCmBgYHtyfQ0Kc2VsZWN0KGltZGIsIHN0YXJ0c193aXRoKCJhdG9yIikpDQpgYGANCg0KUGFyYSByZXRpcmFyIGNvbHVuYXMgZGEgYmFzZSwgYmFzZSBhY3Jlc2NlbnRhciB1bSBgLWAgYW50ZXMgZGEgc2VsZcOnw6NvLg0KDQpgYGB7cn0NCmltZGIgJT4lDQogIHNlbGVjdCgtYW5vLCAtIGRpcmV0b3IpDQppbWRiICU+JQ0KICBzZWxlY3QoLXN0YXJ0c193aXRoKCJhdG9yIikpDQpgYGANCg0KIyMjIyBFeGVyY8OtY2lvcyB7LX0NCg0KVXRpbGl6ZSBhIGJhc2UgYGltZGJgIG5vcyBleGVyY8OtY2lvcyBhIHNlZ3Vpci4NCg0KKioxLioqIFRlc3RlIGFwbGljYXIgYSBmdW7Dp8OjbyBgZ2xpbXBzZSgpYCBkbyBwYWNvdGUgYHtkcGx5cn1gIMOgIGJhc2UgYGltZGJgLiBPIHF1ZSBlbGEgZmF6Pw0KDQoqKjIuKiogQ3JpZSB1bWEgdGFiZWxhIGNvbSBhcGVuYXMgYXMgY29sdW5hcyBgdGl0dWxvYCwgYGRpcmV0b3JgLCBlIGBvcmNhbWVudG8uYCBTYWx2ZSBlbSB1bSBvYmpldG8gY2hhbWFkbyBgaW1kYl9zaW1wbGVzYC4NCg0KKiozLioqIFNlbGVjaW9uZSBhcGVuYXMgYXMgY29sdW5hcyBgYXRvcl8xYCwgYGF0b3JfMmAgZSBgYXRvcl8zYCB1c2FuZG8gbyBhanVkYW50ZSBgY29udGFpbnMoKWAuDQoNCioqNC4qKiBVc2FuZG8gYSBmdW7Dp8OjbyBgc2VsZWN0KClgIChlIHNldXMgYWp1ZGFudGVzKSwgZXNjcmV2YSBjw7NkaWdvcyBxdWUgcmV0b3JuZW0gYSBiYXNlIElNREIgc2VtIGFzIGNvbHVuYXMgYGF0b3JfMWAsIGBhdG9yXzJgIGUgYGF0b3JfMy5gIEVzY3JldmEgdG9kYXMgYXMgc29sdcOnw7VlcyBkaWZlcmVudGVzIHF1ZSB2b2PDqiBjb25zZWd1aXIgcGVuc2FyLiANCg0KIyMjIE9yZGVuYW5kbyBhIGJhc2UNCg0KUGFyYSBvcmRlbmFyIGxpbmhhcywgdXRpbGl6YW1vcyBhIGZ1bsOnw6NvIGBhcnJhbmdlKClgLiBPIHByaW1laXJvIGFyZ3VtZW50byDDqSBhIGJhc2UgZGUgZGFkb3MuIE9zIGRlbWFpcyBhcmd1bWVudG9zIHPDo28gYXMgY29sdW5hcyBwZWxhcyBxdWFpcyBxdWVyZW1vcyBvcmRlbmFyIGFzIGxpbmhhcy4gTm8gZXhlbXBsbyBhIHNlZ3Vpciwgb3JkZW5hbW9zIGFzIGxpbmhhcyBkYSBiYXNlIHBvciBvcmRlbSBjcmVzY2VudGUgZGUgb3LDp2FtZW50by4NCg0KYGBge3J9DQphcnJhbmdlKGltZGIsIG9yY2FtZW50bykNCmBgYA0KDQpUYW1iw6ltIHBvZGVtb3Mgb3JkZW5hciBkZSBmb3JtYSBkZWNyZXNjZW50ZSB1c2FuZG8gYSBmdW7Dp8OjbyBgZGVzYygpYC4NCg0KYGBge3J9DQphcnJhbmdlKGltZGIsIGRlc2Mob3JjYW1lbnRvKSkNCmBgYA0KDQoNCkUgY2xhcm8sIG9yZGVuYXIgc2VndW5kbyBkdWFzIG91IG1haXMgY29sdW5hcy4NCg0KYGBge3J9DQphcnJhbmdlKGltZGIsIGRlc2MoYW5vKSwgZGVzYyhvcmNhbWVudG8pKQ0KYGBgDQoNCiMjIyMgRXhlcmPDrWNpb3Mgey19DQoNClV0aWxpemUgYSBiYXNlIGBpbWRiYCBub3MgZXhlcmPDrWNpb3MgYSBzZWd1aXIuDQoNCioqMS4qKiBPcmRlbmUgb3MgZmlsbWVzIGVtIG9yZGVtIGNyZXNjZW50ZSBkZSBgYW5vYCBlIGRlY3Jlc2NlbnRlIGRlIGByZWNlaXRhYCBlIHNhbHZlIGVtIHVtIG9iamV0byBjaGFtYWRvIGBmaWxtZXNfb3JkZW5hZG9zYC4NCg0KKioyLioqIFNlbGVjaW9uZSBhcGVuYXMgYXMgY29sdW5hcyBgdGl0dWxvYCBlIGBvcmNhbWVudG9gIGUgZW50w6NvIG9yZGVuZSBkZSBmb3JtYSBkZWNyZXNjZW50ZSBwZWxvIGBvcmNhbWVudG8uYA0KDQojIyMgTyBwaXBlIGVtIGHDp8Ojbw0KDQpOYSBncmFuZGUgbWFpb3JpYSBkb3MgY2Fzb3MsIHZhbW9zIGFwbGljYXIgbWFpcyBkZSB1bWEgZnVuw6fDo28gZGUgbWFuaXB1bGHDp8OjbyBlbSB1bWEgYmFzZSBwYXJhIG9idGVybW9zIGEgdGFiZWxhIHF1ZSBkZXNlamFtb3MuIFBvZGVyw61hbW9zLCBwb3IgZXhlbXBsbywgcXVlcmVyIHVtYSB0YWJlbGEgYXBlbmFzIGNvbSBvIHTDrXR1bG8gZSBhbm8gZG9zIGZpbG1lcywgb3JkZW5hZGEgZGUgZm9ybWEgY3Jlc2NlbnRlIGRlIGxhbsOnYW1lbnRvLiBQYXJhIGZhemVyIGlzc28sIHBvZGVyw61hbW9zIGFuaW5oYXIgYXMgZnVuw6fDtWVzDQoNCmBgYHtyfQ0KYXJyYW5nZShzZWxlY3QoaW1kYiwgdGl0dWxvLCBhbm8pLCBhbm8pDQpgYGANCg0Kb3UgY3JpYXIgdW0gb2JqZXRvIGludGVybWVkacOhcmlvIA0KDQpgYGB7cn0NCnRhYl90aXR1bG9fYW5vIDwtIHNlbGVjdChpbWRiLCB0aXR1bG8sIGFubykNCmFycmFuZ2UodGFiX3RpdHVsb19hbm8sIGFubykNCmBgYA0KDQpPcyBkb2lzIGPDs2RpZ29zIGZ1bmNpb25hbSBlIGxldmFtIGFvIG1lc21vIHJlc3VsdGFkbywgbWFzIG7Do28gc8OjbyBtdWl0byBib2FzLg0KDQpBIHByaW1laXJhIGFsdGVybmF0aXZhIMOpIHJ1aW0gZGUgZXNjcmV2ZXIsIGrDoSBxdWUgcHJlY2lzYW1vcyBlc2NyZXZlciBwcmltZWlybyBhIGZ1bsOnw6NvIHF1ZSByb2RhIHBvciDDumx0aW1vLCBlIGRlIGxlciwgcG9pcyDDqSBkaWbDrWNpbCBpZGVudGlmaWNhciBxdWFsIGFyZ3VtZW50byBwZXJ0ZW5jZSBhIHF1YWwgZnVuw6fDo28uDQoNCkEgc2VndW5kYSBhbHRlcm5hdGl2YSDDqSBydWltIHBvaXMgZXhpZ2UgYSBjcmlhw6fDo28gZGUgb2JqZXRvcyBhdXhpbGlhcmVzLiBTZSBxdWlzw6lzc2ltb3MgYXBsaWNhciAxMCBvcGVyYcOnw7VlcyBuYSBiYXNlLCBwcmVjaXNhcsOtYW1vcyBjcmlhciA5IG9iamV0b3MgaW50ZXJtZWRpw6FyaW9zLg0KDQpBIHNvbHXDp8OjbyBwYXJhIGFwbGljYXIgZGl2ZXJzYXMgb3BlcmHDp8O1ZXMgZGUgbWFuaXB1bGHDp8OjbyBlbSB1bWEgYmFzZSBkZSBkYWRvcyDDqSBhcGxpY2FyIG8gb3BlcmFkb3IgcGlwZTogYCU+JWAuDQoNCmBgYHtyfQ0KaW1kYiAlPiUgDQogIHNlbGVjdCh0aXR1bG8sIGFubykgJT4lIA0KICBhcnJhbmdlKGFubykNCmBgYA0KDQpPIHF1ZSBlc3TDoSBzZW5kbyBmZWl0byBubyBjw7NkaWdvIGNvbSBwaXBlPyBEYSBwcmltZWlyYSBwYXJhIGEgc2VndW5kYSBsaW5oYSwgZXN0YW1vcyBhcGxpY2FuZG8gYSBmdW7Dp8OjbyBgc2VsZWN0KClgIMOgIGJhc2UgaW1kYi4gRGEgc2VndW5kYSBwYXJhIGEgdGVyY2VpcmEsIGVzdGFtb3MgYXBsaWNhbmRvIGEgZnVuw6fDo28gYGFycmFuZ2UoKWAgw6AgYmFzZSByZXN1bHRhbnRlIGRhIGZ1bsOnw6NvIGBzZWxlY3QoKWAuDQoNCk8gcmVzdWx0YWRvIGRlc3NlIGPDs2RpZ28gw6kgaWRlbnRpY28gw6BzIHRlbnRhdGl2YXMgc2VtIHBpcGUsIGNvbSBhIHZhbnRhZ2VtIGRlIHRlcm1vcyBlc2NyaXRvIG8gY8OzZGlnbyBuYSBvcmRlbSBlbSBxdWUgYXMgZnVuw6fDtWVzIHPDo28gYXBsaWNhZGFzLCBkZSB0ZXJtb3MgdW0gY8OzZGlnbyBtdWl0byBtYWlzIGxlZ8OtdmVsIGUgZGUgbsOjbyBwcmVjaXNhcm1vcyB1dGlsaXphciBvYmpldG9zIGludGVybWVkacOhcmlvcy4NCg0KIyMjIEZpbHRyYW5kbyBsaW5oYXMNCg0KUGFyYSBmaWx0cmFyIHZhbG9yZXMgZGUgdW1hIGNvbHVuYSBkYSBiYXNlLCB1dGlsaXphbW9zIGEgZnVuw6fDo28gYGZpbHRlcigpYC4NCg0KYGBge3J9DQppbWRiICU+JSBmaWx0ZXIobm90YV9pbWRiID4gOSkNCmBgYA0KDQoNClBvZGVtb3Mgc2VsZWNpb25hciBhcGVuYXMgYXMgY29sdW5hcyB0w610dWxvIGUgbm90YSBwYXJhIHZpc3VhbGl6YXJtb3MgYXMgbm90YXM6DQoNCmBgYHtyfQ0KaW1kYiAlPiUgDQogIGZpbHRlcihub3RhX2ltZGIgPiA5KSAlPiUgDQogIHNlbGVjdCh0aXR1bG8sIG5vdGFfaW1kYikNCmBgYA0KDQpQb2RlbW9zIGVzdGVuZGVyIG8gZmlsdHJvIHBhcmEgZHVhcyBvdSBtYWlzIGNvbHVuYXMuIFBhcmEgaXNzbywgc2VwYXJhbW9zIGNhZGEgb3BlcmHDp8OjbyBwb3IgdW1hIHbDrXJndWxhLg0KDQpgYGB7cn0NCmltZGIgJT4lIGZpbHRlcihhbm8gPiAyMDEwLCBub3RhX2ltZGIgPiA4LjUpDQpgYGANCg0KVGFtYsOpbSBwb2RlbW9zIGZhemVyIG9wZXJhw6fDtWVzIGNvbSBhcyBjb2x1bmFzIGRhIGJhc2UgZGVudHJvIGRhIGZ1bsOnw6NvIGZpbHRlci4gTyBjw7NkaWdvIGFiYWl4byBkZXZvbHZlIHVtYSB0YWJlbGEgYXBlbmFzIGNvbSBvcyBmaWxtZXMgcXVlIGx1Y3JhcmFtLg0KDQpgYGB7cn0NCmltZGIgJT4lIGZpbHRlcihyZWNlaXRhIC0gb3JjYW1lbnRvID4gMCkNCmBgYA0KDQpOYXR1cmFsbWVudGUsIHBvZGVtb3MgZmlsdHJhciBjb2x1bmFzIGNhdGVnw7NyaWNhcy4gTyBleGVtcGxvIGFiYWl4byByZXRvcm5hIHVtYSB0YWJlbGEgYXBlbmFzIGNvbSBvcyBmaWxtZXMgY29tIGEgQW5nZWxpbmEgSm9saWUgUGl0dCBvdSBvIEJyYWQgUGl0dCBubyBwYXBlbCBwcmluY2lwYWwuDQoNCmBgYHtyfQ0KaW1kYiAlPiUNCiAgZmlsdGVyKGF0b3JfMSAlaW4lIGMoJ0FuZ2VsaW5hIEpvbGllIFBpdHQnLCAiQnJhZCBQaXR0IikpDQpgYGANCg0KUGFyYSBmaWx0cmFyIHRleHRvcyBzZW0gY29ycmVzcG9uZMOqbmNpYSBleGF0YSwgcG9kZW1vcyB1dGlsaXphciBhIGZ1bsOnw6NvIGF1eGlsaWFyIGBzdHJfZGV0ZWN0KClgIGRvIHBhY290ZSBge3N0cmluZ3J9YC4gRWxhIHNlcnZlIHBhcmEgdmVyaWZpY2FyIHNlIGNhZGEgc3RyaW5nIGRlIHVtIHZldG9yIGNvbnTDqW0gdW0gZGV0ZXJtaW5hZG8gcGFkcsOjbyBkZSB0ZXh0by4NCg0KYGBge3J9DQpsaWJyYXJ5KHN0cmluZ3IpDQpzdHJfZGV0ZWN0KA0KICBzdHJpbmcgPSBjKCJhIiwgImFhIiwiYWJjIiwgImJjIiwgIkEiLCBOQSksIA0KICBwYXR0ZXJuID0gImEiDQopDQpgYGANCg0KUG9kZW1vcyB1dGlsaXrDoS1sYSBwYXJhIGZpbHRyYXIgYXBlbmFzIG9zIGZpbG1lcyBxdWUgY29udMOqbSBvIGfDqm5lcm8gYcOnw6NvLg0KDQpgYGB7cn0NCiMgQSBjb2x1bmEgZ8OqbmVyb3MgYXByZXNlbnRhIHRvZG9zIG9zIGfDqm5lcm9zIGRvcyBmaWxtZXMgY29uY2F0ZW5hZG9zDQppbWRiJGdlbmVyb3NbMTo2XQ0KIyBQb2RlbW9zIGRldGVjdGFyIHNlIG8gZ8OqbmVybyBBY3Rpb24gYXBhcmVjZSBuYSBzdHJpbmcNCnN0cl9kZXRlY3QoDQogIHN0cmluZyA9IGltZGIkZ2VuZXJvc1sxOjZdLA0KICBwYXR0ZXJuID0gIkFjdGlvbiINCikNCiMgQXBsaWNhbW9zIGVzc2EgbMOzZ2ljYSBkZW50cm8gZGEgZnVuw6fDo28gZmlsdGVyLCBwYXJhIGEgY29sdW5hIGNvbXBsZXRhDQppbWRiICU+JSBmaWx0ZXIoc3RyX2RldGVjdChnZW5lcm9zLCAiQWN0aW9uIikpDQpgYGANCg0KIyMjIyBFeGVyY8OtY2lvcyB7LX0NCg0KVXRpbGl6ZSBhIGJhc2UgYGltZGJgIG5vcyBleGVyY8OtY2lvcyBhIHNlZ3Vpci4NCg0KKioxLioqIENyaWUgdW0gb2JqZXRvIGNoYW1hZG8gYGZpbG1lc19wYmAgYXBlbmFzIGNvbSBmaWxtZXMgcHJldG8gZSBicmFuY28uDQoNCioqMi4qKiBDcmllIHVtIG9iamV0byBjaGFtYWRvIGBjdXJ0b3NfbGVnYWlzYCBjb20gZmlsbWVzIGRlIDkwIG1pbnV0b3Mgb3UgbWVub3MgZGUgZHVyYcOnw6NvIGUgbm90YSBubyBpbWRiIG1haW9yIGRvIHF1ZSA4LjUuDQoNCioqMy4qKiBSZXRvcm5lIHRhYmVsYXMgKGB0aWJibGVzYCkgYXBlbmFzIGNvbToNCg0KLSAqKmEuKiogZmlsbWVzIGNvbG9yaWRvcyBhbnRlcmlvcmVzIGEgMTk1MDsNCg0KLSAqKmIuKiogZmlsbWVzIGRvICJXb29keSBBbGxlbiIgb3UgZG8gIldlcyBBbmRlcnNvbiI7DQoNCi0gKipjLioqIGZpbG1lcyBkbyAiU3RldmVuIFNwaWVsYmVyZyIgb3JkZW5hZG9zIGRlIGZvcm1hIGRlY3Jlc2NlbnRlIHBvciBhbm8sIG1vc3RyYW5kbyBhcGVuYXMgYXMgY29sdW5hcyBgdGl0dWxvYCBlIGBhbm9gOw0KDQotICoqZC4qKiAgZmlsbWVzIHF1ZSB0ZW5oYW0gIkFjdGlvbiIgKipvdSoqICJDb21lZHkiIGVudHJlIG9zIHNldXMgZ8OqbmVyb3M7DQoNCi0gKiplLioqIGZpbG1lcyBxdWUgdGVuaGFtICJBY3Rpb24iICoqZSoqICJDb21lZHkiIGVudHJlIG9zIHNldXMgZ8OqbmVyb3MgZSB0ZW5oYSBgbm90YV9pbWRiYCBtYWlvciBxdWUgODsNCg0KLSAqKmYuKiogZmlsbWVzIHF1ZSBuw6NvIHBvc3N1ZW0gaW5mb3JtYcOnw6NvIHRhbnRvIGRlIHJlY2VpdGEgcXVhbnRvIGRlIG9yw6dhbWVudG8gKGlzdG8gw6ksIHBvc3N1ZW0gYE5BYCBlbSBhbWJhcyBhcyBjb2x1bmFzKS4NCg0KDQojIyMgTW9kaWZpY2FuZG8gZSBjcmlhbmRvIG5vdmFzIGNvbHVuYXMNCg0KUGFyYSBtb2RpZmljYXIgdW1hIGNvbHVuYSBleGlzdGVudGUgb3UgY3JpYXIgdW1hIG5vdmEgY29sdW5hLCB1dGlsaXphbW9zIGEgZnVuw6fDo28gYG11dGF0ZSgpYC4gTyBjw7NkaWdvIGFiYWl4byBkaXZpZGUgb3MgdmFsb3JlcyBkYSBjb2x1bmEgZHVyYcOnw6NvIHBvciA2MCwgbXVkYW5kbyBhIHVuaWRhZGUgZGUgbWVkaWRhIGRlc3NhIHZhcmnDoXZlbCBkZSBtaW51dG9zIHBhcmEgaG9yYXMuDQoNCmBgYHtyfQ0KaW1kYiAlPiUgbXV0YXRlKGR1cmFjYW8gPSBkdXJhY2FvLzYwKQ0KYGBgDQoNClRhbWLDqW0gcG9kZXLDrWFtb3MgdGVyIGNyaWFkbyBlc3NhIHZhcmnDoXZlbCBlbSB1bWEgbm92YSBjb2x1bmEuIFJlcGFyZSBxdWUgYSBub3ZhIGNvbHVuYSBgZHVyYWNhb19ob3Jhc2Agw6kgY29sb2NhZGEgbm8gZmluYWwgZGEgdGFiZWxhLg0KDQpgYGB7cn0NCmltZGIgJT4lIG11dGF0ZShkdXJhY2FvX2hvcmFzID0gZHVyYWNhby82MCkNCmBgYA0KDQpQb2RlbW9zIGZhemVyIHF1YWxxdWVyIG9wZXJhw6fDo28gY29tIHVtYSBvdSBtYWlzIGNvbHVuYXMuIEEgw7puaWNhIHJlZ3JhIMOpIHF1ZSBvIHJlc3VsdGFkbyBkYSBvcGVyYcOnw6NvIHJldG9ybmUgdW0gdmV0b3IgY29tIGNvbXByaW1lbnRvIGlndWFsIGFvIG7Dum1lcm8gZGUgbGluaGFzIGRhIGJhc2UgKG91IGNvbSBjb21wcmltZW50byAxIHBhcmEgZGlzdHJpYnVpciB1bSBtZXNtbyB2YWxvciBlbSB0b2RhcyBhcyBsaW5oYXMpLiBWb2PDqiB0YW1iw6ltIHBvZGUgY3JpYXIvbW9kaWZpY2FyIHF1YW50YXMgY29sdW5hcyBxdWlzZXIgZGVudHJvIGRlIHVtIG1lc21vIGBtdXRhdGVgLg0KDQpgYGB7cn0NCmltZGIgJT4lIA0KICBtdXRhdGUobHVjcm8gPSByZWNlaXRhIC0gb3JjYW1lbnRvLCBwYWlzID0gIkVzdGFkb3MgVW5pZG9zIikgJT4lIA0KICBzZWxlY3QodGl0dWxvLCBsdWNybywgcGFpcykNCmBgYA0KDQojIyMjIEV4ZXJjw61jaW9zIHstfQ0KDQpVdGlsaXplIGEgYmFzZSBgaW1kYmAgbm9zIGV4ZXJjw61jaW9zIGEgc2VndWlyLg0KDQoqKjEuKiogQ3JpZSB1bWEgY29sdW5hIGNoYW1hZGEgYHByZWp1aXpvYCAoYG9yY2FtZW50byAtIHJlY2VpdGFgKSBlIHNhbHZlIGEgbm92YSB0YWJlbGEgZW0gdW0gb2JqZXRvIGNoYW1hZG8gYGltZGJfcHJlanVpem9gLiBFbSBzZWd1aWRhLCBmaWx0cmUgYXBlbmFzIG9zIGZpbG1lcyBxdWUgZGVyYW0gcHJlanXDrXpvIGUgb3JkZW5lIGEgdGFiZWxhIHBvciBvcmRlbSBjcmVzY2VudGUgZGUgcHJlanXDrXpvLg0KDQoqKjIuKiogRmF6ZW5kbyBhcGVuYXMgdW1hIGNoYW1hZGEgZGEgZnVuw6fDo28gbXV0YXRlKCksIGNyaWUgYXMgc2VndWludGVzIGNvbHVuYXMgbm92YXMgbmEgYmFzZSBgaW1kYmA6DQoNCi0gKiphLioqIGBsdWNybyA9IHJlY2VpdGEgLSBvcmNhbWVudG9gDQoNCi0gKipiLioqIGBsdWNyb19tZWRpb2ANCg0KLSAqKmMuKiogYGx1Y3JvX3JlbGF0aXZvID0gKGx1Y3JvIC0gbHVjcm9fbWVkaW8pL2x1Y3JvX21lZGlvYA0KDQotICoqZC4qKiBgaG91dmVfbHVjcm8gPSBpZmVsc2UobHVjcm8gPiAwLCAic2ltIiwgIm7Do28iKWANCg0KKiozLioqIENyaWUgdW1hIG5vdmEgY29sdW5hIHF1ZSBjbGFzc2lmaXF1ZSBvIGZpbG1lIGVtIGAicmVjZW50ZSJgIChwb3N0ZXJpb3IgYSAyMDAwKSBlIGAiYW50aWdvImAgKGRlIDIwMDAgcGFyYSB0csOhcykuDQoNCiMjIyBTdW1tYXJpc2FuZG8gYSBiYXNlDQoNClN1bWFyaXphw6fDo28gw6kgYSB0w6ljbmljYSBkZSBzZSByZXN1bWlyIHVtIGNvbmp1bnRvIGRlIGRhZG9zIHV0aWxpemFuZG8gYWxndW1hIG3DqXRyaWNhIGRlIGludGVyZXNzZS4gQSBtw6lkaWEsIGEgbWVkaWFuYSwgYSB2YXJpw6JuY2lhLCBhIGZyZXF1w6puY2lhLCBhIHByb3BvcsOnw6NvLCBwb3IgZXhlbXBsbywgc8OjbyB0aXBvcyBkZSBzdW1hcml6YcOnw6NvIHF1ZSB0cmF6ZW0gZGlmZXJlbnRlcyBpbmZvcm1hw6fDtWVzIHNvYnJlIHVtYSB2YXJpw6F2ZWwuIA0KDQpQYXJhIHN1bWFyaXphciB1bWEgY29sdW5hIGRhIGJhc2UsIHV0aWxpemFtb3MgYSBmdW7Dp8OjbyBgc3VtbWFyaXplKClgLiBPIGPDs2RpZ28gYWJhaXhvIHJlc3VtZSBhIGNvbHVuYSBvcsOnYW1lbnRvIHBlbGEgc3VhIG3DqWRpYS4NCg0KYGBge3J9DQppbWRiICU+JSBzdW1tYXJpemUobWVkaWFfb3JjYW1lbnRvID0gbWVhbihvcmNhbWVudG8sIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KUmVwYXJlIHF1ZSBhIHNhw61kYSBkYSBmdW7Dp8OjbyBjb250aW51YSBzZW5kbyB1bWEgdGliYmxlLg0KDQpQb2RlbW9zIGNhbGN1bGFyIGRpdmVyc2FzIHN1bWFyaXphw6fDtWVzIGRpZmVyZW50ZXMgZW0gdW0gbWVzbW8gYHN1bW1hcml6ZWAuIENhZGEgc3VtYXJpemHDp8OjbyBzZXLDoSB1bWEgY29sdW5hIGRhIG5vdmEgYmFzZS4NCg0KYGBge3J9DQppbWRiICU+JSBzdW1tYXJpc2UoDQogIG1lZGlhX29yY2FtZW50byA9IG1lYW4ob3JjYW1lbnRvLCBuYS5ybSA9IFRSVUUpLA0KICBtZWRpYW5hX29yY2FtZW50byA9IG1lZGlhbihvcmNhbWVudG8sIG5hLnJtID0gVFJVRSksDQogIHZhcmlhbmNpYV9vcmNhbWVudG8gPSB2YXIob3JjYW1lbnRvLCBuYS5ybSA9IFRSVUUpDQopDQpgYGANCg0KRSB0YW1iw6ltIHN1bWFyaXphciBkaXZlcnNhcyBjb2x1bmFzLg0KDQpgYGB7cn0NCmltZGIgJT4lIHN1bW1hcml6ZSgNCiAgbWVkaWFfb3JjYW1lbnRvID0gbWVhbihvcmNhbWVudG8sIG5hLnJtID0gVFJVRSksDQogIG1lZGlhX3JlY2VpdGEgPSBtZWFuKHJlY2VpdGEsIG5hLnJtID0gVFJVRSksDQogIG1lZGlhX2x1Y3JvID0gbWVhbihyZWNlaXRhIC0gb3JjYW1lbnRvLCBuYS5ybSA9IFRSVUUpDQopDQpgYGANCg0KTXVpdGFzIHZlemVzIHF1ZXJlbW9zIHN1bWFyaXphciB1bWEgY29sdW5hIGFncnVwYWRhIHBlbGFzIGNhdGVnb3JpYXMgZGUgdW1hIHNlZ3VuZGEgY29sdW5hLiBQYXJhIGlzc28sIGFsw6ltIGRvIGBzdW1tYXJpemVgLCB1dGlsaXphbW9zIHRhbWLDqW0gYSBmdW7Dp8OjbyBgZ3JvdXBfYnkoKWAuDQoNCk8gY8OzZGlnbyBhIHNlZ3VpciBjYWxjdWxhIGEgcmVjZWl0YSBtw6lkaWEgZG9zIGZpbG1lcyBwYXJhIGNhZGEgY2F0ZWdvcmlhIGRhIGNvbHVuYSAiY29yIi4NCg0KYGBge3J9DQppbWRiICU+JSANCiAgZ3JvdXBfYnkoY29yKSAlPiUgDQogIHN1bW1hcmlzZShyZWNlaXRhX21lZGlhID0gbWVhbihyZWNlaXRhLCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQoNCkEgw7puaWNhIGFsdGVyYcOnw6NvIHF1ZSBhIGZ1bsOnw6NvIGBncm91cF9ieSgpYCBmYXogbmEgYmFzZSDDqSBhIG1hcmNhw6fDo28gZGUgcXVlIGEgYmFzZSBlc3TDoSBhZ3J1cGFkYS4NCg0KYGBge3J9DQppbWRiICU+JSBncm91cF9ieShjb3IpDQpgYGANCg0KIyMjIyBFeGVyY8OtY2lvcyB7LX0NCg0KVXRpbGl6ZSBhIGJhc2UgYGltZGJgIG5vcyBleGVyY8OtY2lvcyBhIHNlZ3Vpci4NCg0KKioxLioqIENhbGN1bGUgYSBkdXJhw6fDo28gbcOpZGlhIGUgbWVkaWFuYSBkb3MgZmlsbWVzIGRhIGJhc2UuDQoNCioqMi4qKiBDYWxjdWxlIG8gbHVjcm8gbcOpZGlvIGRvcyBmaWxtZXMgY29tIGR1cmHDp8OjbyBtZW5vciBxdWUgNjAgbWludXRvcy4NCg0KKiozLioqIEFwcmVzZW50ZSBuYSBtZXNtYSB0YWJlbGEgbyBsdWNybyBtw6lkaW8gZG9zIGZpbG1lcyBjb20gZHVyYWNhbyBtZW5vciBxdWUgNjAgbWludXRvcyBlIG8gbHVjcm8gbcOpZGlvIGRvcyBmaWxtZXMgY29tIGR1cmFjYW8gbWFpb3Igb3UgaWd1YWwgYSA2MCBtaW51dG9zLg0KDQoqKjQuKiogUmV0b3JuZSB0YWJlbGFzIChgdGliYmxlc2ApIGFwZW5hcyBjb206DQoNCi0gKiphLioqIGEgbm90YSBJTURCIG3DqWRpYSBkb3MgZmlsbWVzIHBvciB0aXBvIGRlIGNsYXNzaWZpY2FjYW87DQoNCi0gKipiLioqIGEgcmVjZWl0YSBtw6lkaWEgZSBtZWRpYW5hIGRvcyBmaWxtZXMgcG9yIGFubzsNCg0KLSAqKmMuKiogYXBlbmFzIG8gbm9tZSBkb3MgZGlyZXRvcmVzIGNvbSBtYWlzIGRlIDEwIGZpbG1lcy4NCg0KIyMjIEp1bnRhbmRvIGR1YXMgYmFzZXMNCg0KUG9kZW1vcyBqdW50YXIgZHVhcyB0YWJlbGFzIGEgcGFydGlyIGRlIHVtYSAoY29sdW5hKSBjaGF2ZSB1dGlsaXphbmRvIGEgZnVuw6fDo28gYGxlZnRfam9pbigpYC4gQ29tbyBleGVtcG8sIHZhbW9zIGluaWNpYWxtZW50ZSBjYWxjdWxhciBvIGx1Y3JvIG3DqWRpbyBkb3MgZmlsbWVzIGRlIGNhZGEgZGlyZXRvciBlIHNhbHZhciBubyBvYmpldG8gYHRhYl9sdWNyb19kaXJldG9yYC4NCg0KYGBge3J9DQp0YWJfbHVjcm9fZGlyZXRvciA8LSBpbWRiICU+JQ0KICBncm91cF9ieShkaXJldG9yKSAlPiUgDQogIHN1bW1hcmlzZShsdWNyb19tZWRpbyA9IG1lYW4ocmVjZWl0YSAtIG9yY2FtZW50bywgbmEucm0gPSBUUlVFKSkNCnRhYl9sdWNyb19kaXJldG9yDQpgYGANCg0KRSBzZSBxdWlzZXJtb3MgY29sb2NhciBlc3NhIGluZm9ybWHDp8OjbyBuYSBiYXNlIG9yaWdpbmFsPyBCYXN0YSB1c2FyIGEgZnVuw6fDo28gYGxlZnRfam9pbigpYCB1dGlsaXphbmRvIGEgY29sdW5hIGBkaXJldG9yYCBjb21vIGNoYXZlLiBPYnNlcnZlIHF1ZSBhIGNvbHVuYSBgbHVjcm9fbWVkaW9gIGFwYXJlY2UgYWdvcmEgbm8gZmltIGRhIHRhYmVsYS4NCg0KYGBge3J9DQppbWRiX2NvbV9sdWNyb19tZWRpbyA8LSBsZWZ0X2pvaW4oaW1kYiwgdGFiX2x1Y3JvX2RpcmV0b3IsIGJ5ID0gImRpcmV0b3IiKQ0KaW1kYl9jb21fbHVjcm9fbWVkaW8NCmBgYA0KDQpOYSB0YWJlbGEgYGltZGJfY29tX2x1Y3JvX21lZGlvYCwgY29tbyBuYSB0YWJlbGEgYGltZGJgLCBjYWRhIGxpbmhhIGNvbnRpbnVhIGEgcmVwcmVzZW50YXIgdW0gZmlsbWUgZGlmZXJlbnRlLCBtYXMgYWdvcmEgdGVtb3MgdGFtYsOpbSBhIGluZm9ybWHDp8OjbyBkbyBsdWNybyBtw6lkaW8gZG8gZGlyZXRvciBkZSBjYWRhIGZpbG1lLiANCg0KQSBwcmltZWlyYSBsaW5oYSwgcG9yIGV4ZW1wbG8sIHRyYXogYXMgaW5mb3JtYcOnw7VlcyBkbyBmaWxtZSBBdmF0YXIuIE8gdmFsb3IgZG8gYGx1Y3JvX21lZGlvYCBuZXNzYSBsaW5oYSByZXByZXNlbnRhIG8gbHVjcm8gbcOpZGlvIGRlIHRvZG9zIG9zIGZpbG1lcyBkbyBKYW1lcyBDYW1lcm9uLCBxdWUgw6kgbyBkaXJldG9yIGRlIEF2YXRhci4gQ29tIGVzc2EgaW5mb3JtYcOnw6NvLCBwb2RlbW9zIGNhbGN1bGFyIG8gcXVhbnRvIG8gbHVjcm8gZG8gQXZhdGFyIHNlIGFmYXN0YSBkbyBsdWNybyBtw6lkaW8gZG8gSmFtZXMgQ2FtZXJvbi4NCg0KYGBge3J9DQppbWRiX2NvbV9sdWNyb19tZWRpbyAlPiUgDQogIG11dGF0ZSgNCiAgICBsdWNybyA9IHJlY2VpdGEgLSBvcmNhbWVudG8sDQogICAgbHVjcm9fcmVsYXRpdm8gPSAobHVjcm8gLSBsdWNyb19tZWRpbykvbHVjcm9fbWVkaW8sDQogICAgbHVjcm9fcmVsYXRpdm8gPSBzY2FsZXM6OnBlcmNlbnQobHVjcm9fcmVsYXRpdm8pDQogICkgJT4lIA0KICBzZWxlY3QodGl0dWxvLCBkaXJldG9yLCBsdWNybywgbHVjcm9fbWVkaW8sIGx1Y3JvX3JlbGF0aXZvKQ0KYGBgDQoNCk9ic2VydmFtb3MgZW50w6NvIHF1ZSBvIEF2YXRhciBvYnRldmUgdW0gbHVjcm8gYXByb3hpbWFkYW1lbnRlIDE2OSUgbWFpb3IgcXVlIGEgbcOpZGlhIGRvcyBmaWxtZXMgZG8gSmFtZXMgQ2FtZXJvbi4NCg0KQWzDqW0gZGEgZnVuw6fDo28gYGxlZnRfam9pbigpYCwgdGFtYsOpbSBzw6NvIG11aXRvIHV0aWxpemFkYXMgYXMgZnVuw6fDtWVzIGByaWdodF9qb2luKClgIGUgYGZ1bGxfam9pbigpYC4NCg0KLSBgcmlnaHRfam9pbigpYDogcmV0b3JuYSB0b2RhcyBhcyBsaW5oYXMgZGEgYmFzZSBgeWAgZSB0b2RhcyBhcyBjb2x1bmFzIGRhcyBiYXNlcyBgeGAgZSBgeWAuIExpbmhhcyBkZSBgeWAgc2VtIGNvcnJlc3BvbmRlbnRlcyBlbSBgeGAgcmVjZWJlcsOjbyBgTkFgIG5hIG5vdmEgYmFzZS4NCg0KLSBgZnVsbF9qb2luKClgOiByZXRvcm5hIHRvZGFzIGFzIGxpbmhhcyBlIGNvbHVuYXMgZGUgYHhgZSBgeWAuIFZhbG9yZXMgc2VtIGNvcnJlc3BvbmTDqm5jaWEgZW50cmUgYXMgYmFzZXMgcmVjZWJlcsOjbyBgTkFgIG5hIG5vdmEgYmFzZS4NCg0KQSBmaWd1cmEgYSBzZWd1aXIgZXNxdWVtYXRpemEgYXMgb3BlcmHDp8O1ZXMgZGVzc2FzIGZ1bsOnw7VlczoNCg0KYGBge3IgZHBseXItam9pbnMsIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30NCiMga25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ2Fzc2V0cy9pbWcvbWFuaXB1bGFjYW8vam9pbnMucG5nJykNCmBgYA0KDQojIyMjIEV4ZXJjw61jaW9zIHstfQ0KDQoqKjEuKiogVXRpbGl6ZSBhIGJhc2UgYGltZGJgIHBhcmEgcmVzb2x2ZXIgb3MgaXRlbnMgYSBzZWd1aXIuDQoNCioqYS4qKiBTYWx2ZSBlbSB1bSBub3ZvIG9iamV0byB1bWEgdGFiZWxhIGNvbSBhDQpub3RhIG3DqWRpYSBkb3MgZmlsbWVzIGRlIGNhZGEgZGlyZXRvci4gRXNzYSB0YWJlbGENCmRldmUgY29udGVyIGR1YXMgY29sdW5hcyAoYGRpcmV0b3JgIGUgYG5vdGFfaW1kYl9tZWRpYWApDQplIGNhZGEgbGluaGEgZGV2ZSBzZXIgdW0gZGlyZXRvciBkaWZlcmVudGUuDQoNCioqYi4qKiBVc2UgbyBgbGVmdF9qb2luKClgIHBhcmEgdHJhemVyIGEgY29sdW5hDQpgbm90YV9pbWRiX21lZGlhYCBkYSB0YWJlbGEgZG8gaXRlbSBhbnRlcmlvcg0KcGFyYSBhIHRhYmVsYSBgaW1kYmAgb3JpZ2luYWwuDQoNCg0KIyMjIGRwbHlyIDEuMA0KDQpBIHZlcnPDo28gMS4wIGRvIHBhY290ZSBgZHBseXJgIGZvaSBvZmljaWFsbWVudGUgbGFuw6dhZGEgZW0ganVuaG8gZGUgMjAyMCBlIGNvbnRvdSBjb20gZGl2ZXJzYXMgbm92aWRhZGVzLiBWYW1vcyBmYWxhciBkYXMgcHJpbmNpcGFpcyBtdWRhbsOnYXM6DQoNCi0gQSBub3ZhIGZ1bsOnw6NvIGBhY3Jvc3MoKWAsIHF1ZSBmYWNpbGl0YSBhcGxpY2FyIHVtYSBtZXNtYSBvcGVyYcOnw6NvIGVtIHbDoXJpYXMgY29sdW5hcy4NCg0KLSBBIHJlcGFnaW5hZGEgZnVuw6fDo28gYHJvd3dpc2UoKWAsIHBhcmEgZmF6ZXIgb3BlcmHDp8O1ZXMgcG9yIGxpbmhhLg0KDQotIE5vdmFzIGZ1bmNpb25hbGlkYWRlcyBkYXMgZnVuw6fDtWVzIGBzZWxlY3QoKWAgZSBgcmVuYW1lKClgIGUgYSBub3ZhIGZ1bsOnw6NvIGByZWxvY2F0ZSgpYC4NCg0KUGFyYSB0cmFiYWxoYXIgZXNzYXMgZnVuw6fDtWVzLCB2YW1vcyB1dGlsaXphciBhIGJhc2UgYGNhc2FzYCBkbyBwYWNvdGUgYGRhZG9zYC4gUGFyYSBpbnN0YWxhciBlc3NlIHBhY290ZSwgcm9kZSBvcyBjw7NkaWdvcyBhYmFpeG86DQoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQojIGluc3RhbGwucGFja2FnZXMoInJlbW90ZXMiKQ0KIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiY2llbmNpYWRlZGF0b3MvZGFkb3MiKQ0KYGBgDQoNClBhcmEgdHJhemVyIG9zIGRhZG9zIHBhcmEgbyBub3NzbyAqZW52aXJvbm1lbnQqLCBwb2RlbW9zIHJvZGFyOg0KDQpgYGB7cn0NCmNhc2FzIDwtIGRhZG9zOjpjYXNhcw0KYGBgDQoNCkEgYmFzZSBgY2FzYXNgIHBvc3N1aSBkYWRvcyBkZSB2ZW5kYSBkZSBjYXNhcyBuYSBjaWRhZGUgZGUgQW1lcywgbm9zIEVzdGFkb3MgVW5pZG9zLiBTw6NvICAyOTMwIGxpbmhhcyBlIDc3IGNvbHVuYXMsIHNlbmRvIHF1ZSBjYWRhIGxpbmhhIGNvcnJlc3BvbmRlIGEgdW1hIGNhc2EgdmVuZGlkYSBlIGNhZGEgY29sdW5hIGEgdW1hIGNhcmFjdGVyw61zdGljYSBkYSBjYXNhIG91IGRhIHZlbmRhLiBFc3NhIHZlcnPDo28gw6kgdW1hIHRyYWR1w6fDo28gZGEgYmFzZSBvcmlnaW5hbCwgcXVlIHBvZGUgc2VyIGVuY29udHJhZGEgbm8gcGFjb3RlIGBBbWVzSG91c2luZ2A6DQoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQppbnN0YWxsLnBhY2thZ2VzKCJBbWVzSG91c2luZyIpDQpkYXRhKGFtZXNfcmF3LCBwYWNrYWdlID0gIkFtZXNIb3VzaW5nIikNCmBgYA0KDQojIyMjIEEgZnVuw6fDo28gYGFjcm9zcygpYCB7LX0NCg0KQSBmdW7Dp8OjbyBgYWNyb3NzKClgIHN1YnN0aXR1aSBhIGZhbcOtbGlhIGRlIHZlcmJvcyBgX2FsbCgpYCwgYF9pZmAgZSBgX2F0KClgLiBBIGlkZWlhIMOpIGZhY2lsaXRhciBhIGFwbGljYcOnw6NvIGRlIHVtYSBvcGVyYcOnw6NvIGEgZGl2ZXJzYXMgY29sdW5hcyBkYSBiYXNlLiBQYXJhIHN1bWFyaXphciBhIGJhc2UgcGFyYSBtYWlzIGRlIHVtYSB2YXJpw6F2ZWwsIGFudGlnYW1lbnRlIHBvZGVyw61hbW9zIGZhemVyOg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICBncm91cF9ieShnZXJhbF9xdWFsaWRhZGUpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbG90ZV9hcmVhX21lZGlhID0gbWVhbihsb3RlX2FyZWEsIG5hLnJtID0gVFJVRSksDQogICAgdmVuZGFfdmFsb3JfbWVkaW8gPSBtZWFuKHZlbmRhX3ZhbG9yLCBuYS5ybSA9IFRSVUUpDQogICkNCmBgYA0KDQpPdSBlbnTDo28gdXRpbGl6YXIgYSBmdW7Dp8OjbyBgc3VtbWFyaXNlX2F0KClgOg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICBncm91cF9ieShnZXJhbF9xdWFsaWRhZGUpICU+JQ0KICBzdW1tYXJpc2VfYXQoDQogICAgLnZhcnMgPSB2YXJzKGxvdGVfYXJlYSwgdmVuZGFfdmFsb3IpLA0KICAgIGxpc3QobWVhbiksDQogICAgbmEucm0gPSBUUlVFDQogICkNCmBgYA0KDQpDb20gYSBub3ZhIGZ1bsOnw6NvIGBhY3Jvc3MoKWAsIGZhemVtb3M6DQoNCmBgYHtyfQ0KY2FzYXMgJT4lDQogIGdyb3VwX2J5KGdlcmFsX3F1YWxpZGFkZSkgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoDQogICAgLmNvbHMgPSBjKGxvdGVfYXJlYSwgdmVuZGFfdmFsb3IpLA0KICAgIC5mbnMgPSBtZWFuLCANCiAgICBuYS5ybSA9IFRSVUUNCiAgKSkNCmBgYA0KDQpBIHNpbnRheGUgw6kgcGFyZWNpZGEgY29tIGEgZnVuw6fDo28gYHN1bW1hcmlzZV9hdCgpYCwgbWFzIGFnb3JhIG7Do28gcHJlY2lzYW1vcyBtYWlzIHVzYXIgYSBmdW7Dp8OjbyBgdmFycygpYCBlIG5lbSB1c2FyIGBsaXN0KG5vbWVfZGFfZnVuY2FvKWBwYXJhIGRlZmluaXIgYSBmdW7Dp8OjbyBhcGxpY2FkYSBuYXMgY29sdW5hcy4NCg0KVXNhbmRvIGBhY3Jvc3MoKWAsIHBvZGVtb3MgZmFjaWxtZW50ZSBhcGxpY2FyIHVtYSBmdW7Dp8OjbyBlbSB0b2RhcyBhcyBjb2x1bmFzIGRhIG5vc3NhIGJhc2UuIEFiYWl4bywgY2FsY3VsYW1vcyBvIG7Dum1lcm8gZGUgdmFsb3JlcyBkaXN0aW50b3MgcGFyYSB0b2RhcyBhcyB2YXJpw6F2ZWlzIGRhIGJhc2UgYGNhc2FzYC4gTyBwYWRyw6NvIGRvIHBhcsOibWV0cm8gYC5jb2xzYCDDqSBgZXZlcml0aGluZygpYCwgcXVlIHJlcHJlc2VudGEgInRvZGFzIGFzIGNvbHVuYXMiLg0KDQpgYGB7cn0NCmNhc2FzICU+JSANCiAgc3VtbWFyaXNlKGFjcm9zcyguZm5zID0gbl9kaXN0aW5jdCkpDQpgYGANCg0KUGFyYSBmYXplciBlc3NhIG1lc21hIG9wZXJhw6fDo28sIGFudGVzIHV0aWxpemFyw61hbW9zIGEgZnVuw6fDo28gYHN1bW1hcmlzZV9hbGwoKWAuDQoNCmBgYHtyfQ0KY2FzYXMgJT4lIA0KICBzdW1tYXJpc2VfYWxsKC5mdW5zID0gfm5fZGlzdGluY3QoLngpKQ0KYGBgDQoNClNlIHF1aXNlcm1vcyBzZWxlY2lvbmFyIGFzIGNvbHVuYXMgYSBzZXJlbSBtb2RpZmljYWRhcyBhIHBhcnRpciBkZSB1bSB0ZXN0ZSBsw7NnaWNvLCB1dGlsaXphbW9zIG8gYWp1ZGFudGUgYHdoZXJlKClgLg0KDQpObyBleGVtcGxvIGFiYWl4bywgY2FsY3VsYW1vcyBvIG7Dum1lcm8gZGUgdmFsb3JlcyBkaXN0aW50b3MgZGFzIGNvbHVuYXMgZGUgY2F0ZWfDs3JpY2FzLg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKHdoZXJlKGlzLmNoYXJhY3RlciksIG5fZGlzdGluY3QpKQ0KYGBgDQoNClRvZGFzIGFzIGNvbHVuYXMgZGEgYmFzZSByZXN1bHRhbnRlIGVyYW0gY29sdW5hcyBjb20gY2xhc3NlIGBjaGFyYWN0ZXJgIG5hIGJhc2UgYGNhc2FzYC4NCg0KQW50ZXJpb3JtZW50ZSwgdXRpbGl6w6F2YW1vcyBhIGZ1bsOnw6NvIGBzdW1tYXJpc2VfaWYoKWAuDQoNCmBgYHtyfQ0KY2FzYXMgJT4lDQogIHN1bW1hcmlzZV9pZihpcy5jaGFyYWN0ZXIsIG5fZGlzdGluY3QpDQpgYGANCg0KQ29tIG8gYGFjcm9zcygpYCwgcG9kZW1vcyBjb21iaW5hciBvcyBlZmVpdG9zIGRlIHVtIGBzdW1tYXJpc2VfaWYoKWAgZSB1bSBgc3VtbWFyaXNlX2F0KClgIGVtIHVtIMO6bmljbyBgc3VtbWFyaXNlKClgLiBBIHNlZ3VpciwgY2FsY3VsYW1vcyBhcyDDoXJlYXMgbcOpZGlhcywgZ2FyYW50aW5kbyBxdWUgcGVnYW1vcyBhcGVuYXMgdmFyacOhdmVpcyBudW3DqXJpY2FzLg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpICYgY29udGFpbnMoIl9hcmVhIiksIG1lYW4sIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KQWzDqW0gZGlzc28sIGNvbSBhIGZ1bsOnw6NvIGBhY3Jvc3MoKWAsIHBvZGVtb3MgZmF6ZXIgc3VtYXJpemHDp8O1ZXMgY29tcGxleGFzIHF1ZSBuw6NvIHNlcmlhIHBvc3PDrXZlbCB1dGlsaXphbmRvIGFwZW5hcyBhcyBmdW7Dp8O1ZXMgYHN1bW1hcmlzZSgpYCwgYHN1bW1hcmlzZV9pZigpYCBlIGBzdW1tYXJpc2VfYXQoKWAuIE5vIGV4ZW1wbG8gYSBzZWd1aXIsIGNhbGN1bGFtb3MgYSBtw6lkaWEgZGFzIMOhcmVhcywgbyBuw7ptZXJvIGRlIGBOQXNgIGRlIHZhcmnDoXZlaXMgY2F0ZWfDs3JpY2FzIGUgbyBuw7ptZXJvIGRlIGNhc2FzIHBhcmEgY2FkYSB0aXBvIGRlIGZ1bmRhw6fDo28uIFR1ZG8gZW0gdW0gbWVzbW8gYHN1bW1hcmlzZSgpYCENCg0KYGBge3J9DQpjYXNhcyAlPiUNCiAgZ3JvdXBfYnkoZnVuZGFjYW9fdGlwbykgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYykgJiBjb250YWlucygiYXJlYSIpLCBtZWFuLCBuYS5ybSA9IFRSVUUpLA0KICAgIGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCB+c3VtKGlzLm5hKC54KSkpLA0KICAgIG5fb2JzID0gbigpLA0KICApICU+JSANCiAgc2VsZWN0KDE6NCwgbl9vYnMpDQpgYGANCg0KRW1ib3JhIGEgbm92YSBzaW50YXhlLCB1c2FuZG8gYGFjcm9zcygpYCwgbsOjbyBzZWphIG11aXRvIGRpZmVyZW50ZSBkbyBxdWUgZmF6w61hbW9zIGFudGVzLCByZWFsaXphciBzdW1hcml6YcOnw7VlcyBjb21wbGV4YXMgbsOjbyDDqSBhIMO6bmljYSB2YW50YWdlbSBkZXNzZSBub3ZvICpmcmFtZXdvcmsqLg0KDQpPIGBhY3Jvc3MoKWAgcG9kZSBzZXIgdXRpbGl6YWRvIGVtIHRvZG9zIG9zIHZlcmJvcyBkbyBge2RwbHlyfWAgKGNvbSBleGNlw6fDo28gZG8gYHNlbGVjdCgpYCBlIGByZW5hbWUoKWAsIGrDoSBxdWUgZWxlIG7Do28gdHLDoXMgdmFudGFnZW5zIGNvbSByZWxhw6fDo28gYW8gcXVlIGrDoSBwb2RpYSBzZXIgZmVpdG8pIGUgaXNzbyB1bmlmaWNhIG8gbW9kbyBkZSBmYXplcm1vcyBlc3NhcyBvcGVyYcOnw7VlcyBubyBgZHBseXJgLiBFbSB2ZXogZGUgdGVybW9zIHVtYSBmYW3DrWxpYSBkZSBmdW7Dp8O1ZXMgcGFyYSBjYWRhIHZlcmJvLCB0ZW1vcyBhZ29yYSBhcGVuYXMgbyBwcsOzcHJpbyB2ZXJibyBlIGEgZnVuw6fDo28gYGFjcm9zcygpYC4NCg0KVmFtb3MgdmVyIHVtIGV4ZW1wbG8gcGFyYSBvIGBtdXRhdGUoKWAgZSBwYXJhIG8gYGZpbHRlcigpYC4NCg0KTyBjw7NkaWdvIGFiYWl4byB0cmFuc2Zvcm1hIHRvZGFzIGFzIHZhcmnDoXZlaXMgcXVlIHBvc3N1ZW0gImFyZWEiIG5vIG5vbWUsIHBhc3NhbmRvIG9zIHZhbG9yZXMgZGUgcMOpcyBxdWFkcmFkb3MgcGFyYSBtZXRyb3MgcXVhZHJhZG9zLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KY2FzYXMgJT4lDQogIG11dGF0ZShhY3Jvc3MoDQogICAgY29udGFpbnMoImFyZWEiKSwNCiAgICB+IC54IC8gMTAuNzY0DQogICkpDQpgYGANCg0KSsOhIG8gY8OzZGlnbyBhIHNlZ3VpciBmaWx0cmEgYXBlbmFzIGFzIGNhc2FzIHF1ZSBwb3NzdWVtIHZhcmFuZGEgYWJlcnRhLCBjZXJjYSBlIGxhcmVpcmEgKG8gYE5BYCBuZXNzYSBiYXNlIHNpZ25pZmljYSBxdWUgYSBjYXNhIG7Do28gcG9zc3VpIHRhbCBjYXJhY3RlcsOtc3RpY2EpLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KY2FzYXMgJT4lDQogIGZpbHRlcihhY3Jvc3MoDQogICAgYyh2YXJhbmRhX2FiZXJ0YV9hcmVhLCBjZXJjYV9xdWFsaWRhZGUsIGxhcmVpcmFfcXVhbGlkYWRlKSwNCiAgICB+IWlzLm5hKC54KQ0KICApKSANCmBgYA0KDQpOw6NvIHByZWNpc2Ftb3MgZG8gYGFjcm9zcygpYCBuYSBob3JhIGRlIHNlbGVjaW9uYXIgY29sdW5hcy4gQSBmdW7Dp8OjbyBgc2VsZWN0KClgIGrDoSB1c2EgbmF0dXJhbG1lbnRlIG8gbWVjYW5pc21vIGRlIHNlbGXDp8OjbyBkZSBjb2x1bmFzIHF1ZSBvIGBhY3Jvc3MoKWAgcHJvcG9yY2lvbmEuDQoNCmBgYHtyfQ0KY2FzYXMgJT4lDQogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkNCmBgYA0KDQpPIG1lc21vIHZhbGUgcGFyYSBvIGByZW5hbWUoKWAuIFNlIHF1aXNlcm1vcyByZW5vbWVyIHbDoXJpYXMgY29sdW5hcywgYSBwYXJ0aXIgZGUgdW1hIGZ1bsOnw6NvLCB1dGlsaXphbW9zIG8gYHJlbmFtZV93aXRoKClgLg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICByZW5hbWVfd2l0aCh0b3VwcGVyLCBjb250YWlucygidmVuZGEiKSkNCmBgYA0KDQojIyMjIEEgZnVuw6fDo28gYHJlbG9jYXRlKClgIHstfQ0KDQpPIGB7ZHBseXJ9YCBwb3NzdWkgYWdvcmEgdW1hIGZ1bsOnw6NvIHByw7NwcmlhIHBhcmEgcmVvcmdhbml6YXIgY29sdW5hczogYHJlbG9jYXRlKClgLiBQb3IgcGFkcsOjbywgZWxhIGNvbG9jYSB1bWEgb3UgbWFpcyBjb2x1bmFzIG5vIGNvbWXDp28gZGEgYmFzZS4NCg0KYGBge3J9DQpjYXNhcyAlPiUNCiAgcmVsb2NhdGUodmVuZGFfdmFsb3IsIHZlbmRhX3RpcG8pDQpgYGANCg0KUG9kZW1vcyB1c2FyIG9zIGFyZ3VtZW50b3MgYC5hZnRlcmAgZSBgLmJlZm9yZWAgcGFyYSBmYXplciBtdWRhbsOnYXMgbWFpcyBjb21wbGV4YXMuDQoNCk8gY8OzZGlnbyBiYWl4byBjb2xvY2EgYSBjb2x1bmEgYHZlbmRhX2Fub2AgZGVwb2lzIGRhIGNvbHVuYSBgY29uc3RydWNhb19hbm9gLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KY2FzYXMgJT4lDQogIHJlbG9jYXRlKHZlbmRhX2FubywgLmFmdGVyID0gY29uc3RydWNhb19hbm8pDQpgYGANCg0KDQpPIGPDs2RpZ28gYmFpeG8gY29sb2NhIGEgY29sdW5hIGB2ZW5kYV9hbm9gIGFudGVzIGRhIGNvbHVuYSBgY29uc3RydWNhb19hbm9gLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KY2FzYXMgJT4lDQogIHJlbG9jYXRlKHZlbmRhX2FubywgLmJlZm9yZSA9IGNvbnN0cnVjYW9fYW5vKQ0KYGBgDQoNCiMjIyMgQSBmdW7Dp8OjbyBgcm93d2lzZSgpYCB7LX0NCg0KUG9yIGZpbSwgdmFtb3MgZGlzY3V0aXIgb3BlcmHDp8O1ZXMgZmVpdGFzIHBvciBsaW5oYS4gVG9tZSBjb21vIGV4ZW1wbG8gYSB0YWJlbGEgYWJhaXhvLiBFbGEgYXByZXNlbnRhIGFzIG5vdGFzIGRlIGFsdW5vcyBlbSBxdWF0cm8gcHJvdmFzLg0KDQpgYGB7cn0NCnRhYl9ub3RhcyA8LSB0aWJibGUoDQogIHN0dWRlbnRfaWQgPSAxOjUsDQogIHByb3ZhMSA9IHNhbXBsZSgwOjEwLCA1KSwNCiAgcHJvdmEyID0gc2FtcGxlKDA6MTAsIDUpLA0KICBwcm92YTMgPSBzYW1wbGUoMDoxMCwgNSksDQogIHByb3ZhNCA9IHNhbXBsZSgwOjEwLCA1KQ0KKQ0KdGFiX25vdGFzDQpgYGANCg0KU2UgcXVpc2VybW9zIGdlcmFyIHVtYSBjb2x1bmEgY29tIGEgbm90YSBtw6lkaWEgZGUgY2FkYSBhbHVubyBuYXMgcXVhdHJvIHByb3ZhcywgbsOjbyBwb2RlcsOtYW1vcyB1c2FyIG8gYG11dGF0ZSgpYCBkaXJldGFtZW50ZS4NCg0KYGBge3J9DQp0YWJfbm90YXMgJT4lIG11dGF0ZShtZWRpYSA9IG1lYW4oYyhwcm92YTEsIHByb3ZhMiwgcHJvdmEzLCBwcm92YTQpKSkNCmBgYA0KDQpOZXN0ZSBjYXNvLCB0b2RhcyBhcyBjb2x1bmFzIGVzdMOjbyBzZW5kbyBlbXBpbGhhZGFzIGUgZ2VyYW5kbyB1bWEgw7puaWNhIG3DqWRpYSwgcGFzc2FkYSBhIHRvZGFzIGFzIGxpbmhhcyBkYSBjb2x1bmEgYG1lZGlhYC4NCg0KUGFyYSBmYXplcm1vcyBhIGNvbnRhIHBhcmEgY2FkYSBhbHVubywgcG9kZW1vcyBhZ3J1cGFyIHBvciBhbHVuby4gQWdvcmEgc2ltIGEgbcOpZGlhIMOpIGNhbGN1bGFkYSBhcGVuYXMgbmFzIG5vdGFzIGRlIGNhZGEgZXN0dWRhbnRlLg0KDQpgYGB7cn0NCnRhYl9ub3RhcyAlPiUNCiAgZ3JvdXBfYnkoc3R1ZGVudF9pZCkgJT4lDQogIG11dGF0ZShtZWRpYSA9IG1lYW4oYyhwcm92YTEsIHByb3ZhMiwgcHJvdmEzLCBwcm92YTQpKSkNCmBgYA0KDQpUYW1iw6ltIHBvZGVtb3Mgbm9zIGFwcm92ZWl0YXIgZGEgc2ludGF4ZSBkbyBgYWNyb3NzKClgIG5lc3RlIGNhc28uIFBhcmEgaXNzbywgcHJlY2lzYW1vcyBzdWJzdHV0aXIgYSBmdW7Dp8OjbyBgYygpYCBwZWxhIGZ1bsOnw6NvIGBjX2Fjcm9zcygpYC4NCg0KYGBge3J9DQp0YWJfbm90YXMgJT4lDQogIGdyb3VwX2J5KHN0dWRlbnRfaWQpICU+JQ0KICBtdXRhdGUobWVkaWEgPSBtZWFuKGNfYWNyb3NzKHN0YXJ0c193aXRoKCJwcm92YSIpKSkpDQpgYGANCg0KRXF1aXZhbGVudGVtZW50ZSBhbyBgZ3JvdXBfYnkoKWAsIG5lc3RlIGNhc28sIHBvZGVtb3MgdXNhciBhIGZ1bsOnw6NvIGByb3d3aXNlKClgLg0KDQpgYGB7cn0NCnRhYl9ub3RhcyAlPiUNCiAgcm93d2lzZShzdHVkZW50X2lkKSAlPiUNCiAgbXV0YXRlKG1lZGlhID0gbWVhbihjX2Fjcm9zcyhzdGFydHNfd2l0aCgicHJvdmEiKSkpKQ0KYGBgDQoNCkVsYSDDqSBtdWl0byDDunRpbCBxdWFuZG8gcXVlcmVtb3MgZmF6ZXIgb3BlcmHDp8OjbyBwb3IgbGluaGFzLCBtYXMgbsOjbyB0ZW1vcyB1bWEgY29sdW5hIGRlIGlkZW50aWZpY2HDp8Ojby4gUG9yIHBhZHLDo28sIHNlIG7Do28gaW5kaWNhcm1vcyBuZW5odW1hIGNvbHVuYSwgY2FkYSBsaW5oYSBzZXLDoSB1bSAiZ3J1cG8iLg0KDQpgYGB7cn0NCnRhYl9ub3RhcyAlPiUNCiAgcm93d2lzZSgpICU+JQ0KICBtdXRhdGUobWVkaWEgPSBtZWFuKGNfYWNyb3NzKHN0YXJ0c193aXRoKCJwcm92YSIpKSkpDQpgYGANCg0KVmVqYSBxdWUgYHN0dWRlbnRfaWRgIG7Do28gw6kgcGFzc2FkYSBwYXJhIGEgZnVuw6fDo28gYHJvd3dpc2UoKWAuIE7Do28gcHJlY2lzYXLDrWFtb3MgZGVzc2EgY29sdW5hIG5hIGJhc2UgcGFyYSByZXByb2R1emlyIGEgZ2VyYcOnw6NvIGRhIGNvbHVtYSBgbWVkaWFgIG5lc3RlIGNhc28uDQoNCiMjIyMgRXhlcmPDrWNpb3Mgey19DQoNCkEgYmFzZSBgY2FzYXNgIGFiYWl4byBwb2RlIHNlciBlbmNvbnRyYWRhIGEgcGFydGlyIGRvIGPDs2RpZ28gYWJhaXhvOg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImNpZW5jaWFkZWRhdG9zL2RhZG9zIikNCmxpYnJhcnkoZGFkb3MpDQpkYWRvczo6Y2FzYXMNCmBgYA0KDQoNCioqMS4qKiBSZWVzY3JldmEgb3MgY8OzZGlnb3MgYWJhaXhvIHV0aWxpemFuZG8gYXMgZnVuw6fDtWVzIGBhY3Jvc3MoKWAgZSBgd2hlcmUoKWAuDQoNCioqYS4qKiANCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmNhc2FzICU+JQ0KICBncm91cF9ieShnZXJhbF9xdWFsaWRhZGUpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgYWNpbWFfc29sb19hcmVhX21lZGlhID0gbWVhbihhY2ltYV9zb2xvX2FyZWEsIG5hLnJtID0gVFJVRSksDQogICAgZ2FyYWdlbV9hcmVhX21lZGlhID0gbWVhbihnYXJhZ2VtX2FyZWEsIG5hLnJtID0gVFJVRSksDQogICAgdmFsb3JfdmVuZGFfbWVkaW8gPSBtZWFuKHZlbmRhX3ZhbG9yLCBuYS5ybSA9IFRSVUUpDQogICkNCmBgYA0KDQoqKmIuKioNCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmNhc2FzICU+JQ0KICBmaWx0ZXJfYXQoDQogICAgdmFycyhwb3Jhb19xdWFsaWRhZGUsIHZhcmFuZGFfZmVjaGFkYV9hcmVhLCBjZXJjYV9xdWFsaWRhZGUpLA0KICAgIH4haXMubmEoLngpDQogICkNCmBgYA0KDQoqKmMuKioNCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmNhc2FzICU+JQ0KICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCB+dGlkeXI6OnJlcGxhY2VfbmEoLngsIHJlcGxhY2UgPSAiTsOjbyBwb3NzdWkiKSkNCmBgYA0KDQoqKjIuKiogVXRpbGl6YW5kbyBhIGJhc2UgYGNhc2FzYCwgcmVzb2x2YSBvcyBpdGVucyBhIHNlZ3Vpci4NCg0KLSAqKmEuKiogVXNhbmRvIG8gYGNhc2Vfd2hlbigpYCBjcmllIHVtIGPDs2RpZ28gcGFyYSBjYXRlZ29yaXphciBhIHZhcmnDoXZlbCB2ZW5kYV92YWxvciBkYSBzZWd1aW50ZSBtYW5laXJhOg0KDQogIC0gKipiYXJhdGEqKjogXCQwIGEgXCQxMjkuNTAwICANCiAgLSAqKnByZcOnbyBtZWRpYW5vKio6IFwkMTI5LjUwMCBhIFwkMTgwLjc5Ng0KICAtICoqY2FyYSoqOiBcJCAxODAuNzk2IGEgXCQyMTMuNTAwDQogIC0gKiptdWl0byBjYXJhKio6IG1haW9yIHF1ZSBcJDIxMy41MDANCg0KPGRpdiBzdHlsZSA9ICJoZWlnaHQ6IDEwcHg7Ij48L2Rpdj4NCg0KLSAqKmIuKiogVXRpbGl6ZSBvIGPDs2RpZ28gZmVpdG8gbmEgbGV0cmEgKGEpIHBhcmEgYWdydXBhciBhIGJhc2UgYGNhc2FzYCBwZWxhIHZhcmnDoXZlbCB2ZW5kYV92YWxvciBjYXRlZ29yaXphZGEgZSBjYWxjdWxhciB0b2RhcyBhcyDDoXJlYXMgbcOpZGlhcyBwYXJhIGNhZGEgdW1hIGRlc3NhcyBjYXRlZ29yaWFzLg0KDQoqKjMuKiogRXNjcmV2YSB1bSBjw7NkaWdvIHF1ZSByZWNlYmEgYSBiYXNlIGBjYXNhc2AgZSByZXRvcm5lIHVtYSB0YWJlbGEgY29tIGFwZW5hcw0KDQotICoqYS4qKiBhcyBjb2x1bmFzIHJlZmVyZW50ZXMgw6AgZ2FyYWdlbSBkYSBjYXNhLg0KDQotICoqYi4qKiBhcyBjb2x1bmFzIHJlZmVyZW50ZXMgYSB2YXJpw6F2ZWlzIGRlIHF1YWxpZGFkZS4NCg0KLSAqKmMuKiogY29sdW5hcyBudW3DqXJpY2FzIHF1ZSByZXByZXNlbnRhbSDDoXJlYXMgZGEgY2FzYSBlIGRvIHRlcnJlbm8uDQoNCi0gKipkLioqIGNvbHVuYXMgbnVtw6lyaWNhcy4NCg0KLSAqKmUuKiogY29sdW5hcyByZWZlcmVudGVzIMOgIHBpc2NpbmEsIHBvcsOjbyBlIG8gdmFsb3IgZGUgdmVuZGEuDQoNCioqNC4qKiBVc2FuZG8gYSBmdW7Dp8OjbyBgcmVuYW1lX3dpdGgoKWAsIHRyb3F1ZSB0b2RvcyBvcyBgIl8iYCBkb3Mgbm9tZXMgZGFzIGNvbHVuYXMgcG9yIHVtIGVzcGHDp28gYCIgImAuDQoNCioqNS4qKiBFc2NyZXZhIHVtIGPDs2RpZ28gcGFyYSBjb2xvY2FyIHRvZGFzIGFzIGNvbHVuYXMgcmVsYXRpdmFzIGEgdmVuZGEgbm8gY29tZcOnbyBkYSBiYXNlIGBjYXNhc2AuDQoNCioqNi4qKiA1LiBFc2NyZXZhIHVtIGPDs2RpZ28gcGFyYSBjb2xvY2FyIHRvZGFzIGFzIGNvbHVuYXMgbnVtw6lyaWNhcyBkYSBiYXNlIGBjYXNhc2Agbm8gY29tZcOnbyBkYSB0YWJlbGEgZSB0b2RhcyBhcyBjb2x1bmFzIGNhdGVnw7NyaWNhcyBubyBmaW5hbC4NCg==